pg.ddx.io  pgsql-hackers@postgresql.org mailing list archive  
help / color / mirror / Atom feed
speeding up planning with partitions
265+ messages / 18 participants
[nested] [flat]

Commits from this thread:
  2019-03-30 428b260f87  Speed up planning when partitions can be pruned at plan time.
  2019-03-27 333ed246c6  Avoid passing query tlist around separately from root->processed_tlist.
  2019-03-26 53bcf5e3db  Build "other rels" of appendrel baserels in a separate step.
  2019-03-26 e8d5dd6be7  Get rid of duplicate child RTE for a partitioned table.
  2019-03-22 734308a220  Rearrange make_partitionedrel_pruneinfo to avoid work when we can't prune.
  2019-03-22 c8151e6423  Don't copy PartitionBoundInfo in set_relation_partition_info.
  2019-01-16 d723f56872  Reorganize planner code moved in b60c39759908
  2019-01-10 b60c397599  Move inheritance expansion code into its own file
* speeding up planning with partitions
@ 2018-08-29 12:06  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 7 replies; 265+ messages in thread

From: Amit Langote @ 2018-08-29 12:06 UTC (permalink / raw)
  To: pgsql-hackers

It is more or less well known that the planner doesn't perform well with
more than a few hundred partitions even when only a handful of partitions
are ultimately included in the plan.  Situation has improved a bit in PG
11 where we replaced the older method of pruning partitions one-by-one
using constraint exclusion with a much faster method that finds relevant
partitions by using partitioning metadata.  However, we could only use it
for SELECT queries, because UPDATE/DELETE are handled by a completely
different code path, whose structure doesn't allow it to call the new
pruning module's functionality.  Actually, not being able to use the new
pruning is not the only problem for UPDATE/DELETE, more on which further
below.

While situation improved with new pruning where it could, there are still
overheads in the way planner handles partitions.  As things stand today,
it will spend cycles and allocate memory for partitions even before
pruning is performed, meaning most of that effort could be for partitions
that were better left untouched.  Currently, planner will lock, heap_open
*all* partitions, create range table entries and AppendRelInfos  for them,
and finally initialize RelOptInfos for them, even touching the disk file
of each partition in the process, in an earlier phase of planning.  All of
that processing is vain for partitions that are pruned, because they won't
be included in the final plan.  This problem grows worse as the number of
partitions grows beyond thousands, because range table grows too big.

That could be fixed by delaying all that per-partition activity to a point
where pruning has already been performed, so that we know the partitions
to open and create planning data structures for, such as somewhere
downstream to query_planner.  But before we can do that we must do
something about the fact that UPDATE/DELETE won't be able to cope with
that because the code path that currently handles the planning of
UPDATE/DELETE on partitioned tables (inheritance_planner called from
subquery_planner) relies on AppendRelInfos for all partitions having been
initialized by an earlier planning phase.  Delaying it to query_planner
would be too late, because inheritance_planner calls query_planner for
each partition, not for the parent.  That is, if query_planner, which is
downstream to inheritance_planner, was in the charge of determining which
partitions to open, the latter wouldn't know which partitions to call the
former for. :)

That would be fixed if there is no longer this ordering dependency, which
is what I propose to do with the attached patch 0001.  I've tried to
describe how the patch manages to do that in its commit message, but I'll
summarize here.  As things stand today, inheritance_planner modifies the
query for each leaf partition to make the partition act as the query's
result relation instead of the original partitioned table and calls
grouping_planner on the query.  That means anything that's joined to
partitioned table looks to instead be joined to the partition and join
paths are generated likewise.  Also, the resulting path's targetlist is
adjusted to be suitable for the result partition.  Upon studying how this
works, I concluded that the same result can be achieved if we call
grouping_planner only once and repeat the portions of query_planner's and
grouping_planner's processing that generate the join paths and appropriate
target list, respectively, for each partition.  That way, we can rely on
query_planner determining result partitions for us, which in turn relies
on the faster partprune.c based method of pruning.  That speeds things up
in two ways.  Faster pruning and we no longer repeat common processing for
each partition.


With 0001 in place, there is nothing that requires that partitions be
opened by an earlier planning phase, so, I propose patch 0002, which
refactors the opening and creation of planner data structures for
partitions such that it is now performed after pruning. However, it
doesn't do anything about the fact that partitions are all still locked in
the earlier phase.

With various overheads gone thanks to 0001 and 0002, locking of all
partitions via find_all_inheritos can be seen as the single largest
bottleneck, which 0003 tries to address.  I've kept it a separate patch,
because I'll need to think a bit more to say that it's actually to safe to
defer locking to late planning, due mainly to the concern about the change
in the order of locking from the current method.  I'm attaching it here,
because I also want to show the performance improvement we can expect with it.


I measured the gain in performance due to each patch on a modest virtual
machine.  Details of the measurement and results follow.

* Benchmark scripts

update.sql
update ht set a = 0 where b = 1;

select.sql
select * from ht where b = 1;

* Table:

create table ht (a int, b int) partition by hash (b)
create table ht_1 partition of ht for values with (modulus N, remainder 0)
..
create table ht_N partition of ht for values with (modulus N, remainder N-1)

* Rounded tps with update.sql and select.sql against regular table (nparts
= 0) and partitioned table with various partition counts:

pgbench -n -T 60 -f update.sql

nparts  master    0001   0002   0003
======  ======    ====   ====   ====
0         2856    2893   2862   2816
8          507    1115   1447   1872
16         260     765   1173   1892
32         119     483    922   1884
64          59     282    615   1881
128         29     153    378   1835
256         14      79    210   1803
512          5      40    113   1728
1024         2      17     57   1616
2048         0*      9     30   1471
4096         0+      4     15   1236
8192         0=      2      7    975

* 0.46
+ 0.0064
= 0 (OOM on a virtual machine with 4GB RAM)

As can be seen here, 0001 is a big help for update queries.

pgbench -n -T 60 -f select.sql

For a select query that doesn't contain join and needs to scan only one
partition:

nparts  master    0001   0002   0003
======  ======    ====   ====   ====
0         2290    2329   2319   2268
8         1058    1077   1414   1788
16         711     729   1124   1789
32         450     475    879   1773
64         265     272    603   1765
128        146     149    371   1685
256	    76      77    214   1678
512	    39      39    112   1636
1024	    16      17     59   1525
2048	     8       9     29   1416
4096	     4       4     15   1195
8192	     2       2      7    932

Actually, here we get almost same numbers with 0001 as with master,
because 0001 changes nothing for SELECT queries.  We start seeing
improvement with 0002, the patch to delay opening partitions.

Thanks,
Amit

Attachments: download all (3 files, .tar.gz)

0001-Overhaul-partitioned-table-update-delete-planning.patch (text/plain, 26.7K) download From 060bd2445ea9cba9adadd73505689d6f06583ee8 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 24 Aug 2018 12:39:36 +0900 Subject: [PATCH 1/3] Overhaul partitioned table update/delete planning Current method, inheritance_planner, applies grouping_planner and hence query_planner to the query repeatedly with each leaf partition replacing the root parent as the query's result relation. One big drawback of this approach is that it cannot use partprune.c to perform partition pruning on the partitioned result relation, because it can only be invoked if query_planner sees the partitioned relation itself in the query. That is not true with the existing method, because as mentioned above, query_planner is invoked with the partitioned relation replaced with individual leaf partitions. While most of the work in each repitition of grouping_planner (and query_planner) is same, a couple of things may differ from partition to partition -- 1. Join planning may produce different Paths for joining against different result partitions, 2. grouping_planner may produce different top-level target lists for different partitions, based on their TupleDescs. This commit rearranges things so that, only the planning steps that affect 1 and 2 above are repeated for partitions that are selected by query_planner by applying partprune.c based pruning to the original partitioned result rel. That makes things faster because 1. partprune.c based pruning is used instead of using constraint exclusion for each partition, 2. grouping_planner (and query_planner) is invoked only once instead of for every partition thus saving cycles and memory. This still doesn't help much if no partitions are pruned, because we still repeat join planning and makes copies of the query for each partition, but for common cases where only handful partitions remain after pruning, this makes things significanly faster. --- doc/src/sgml/ddl.sgml | 15 +- src/backend/optimizer/path/allpaths.c | 97 ++++++- src/backend/optimizer/plan/planmain.c | 4 +- src/backend/optimizer/plan/planner.c | 378 ++++++++++++++++++++------- src/backend/optimizer/prep/prepunion.c | 28 +- src/backend/optimizer/util/plancat.c | 30 --- src/test/regress/expected/partition_join.out | 4 +- 7 files changed, 416 insertions(+), 140 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index b5ed1b7939..53c479fbb8 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -3933,16 +3933,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <xref linkend="guc-enable-partition-pruning"/> setting. </para> - <note> - <para> - Currently, pruning of partitions during the planning of an - <command>UPDATE</command> or <command>DELETE</command> command is - implemented using the constraint exclusion method (however, it is - controlled by the <literal>enable_partition_pruning</literal> rather than - <literal>constraint_exclusion</literal>) &mdash; see the following section - for details and caveats that apply. - </para> - <para> Execution-time partition pruning currently occurs for the <literal>Append</literal> and <literal>MergeAppend</literal> node types. @@ -3964,9 +3954,8 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <para> <firstterm>Constraint exclusion</firstterm> is a query optimization - technique similar to partition pruning. While it is primarily used - for partitioning implemented using the legacy inheritance method, it can be - used for other purposes, including with declarative partitioning. + technique similar to partition pruning. It is primarily used + for partitioning implemented using the legacy inheritance method. </para> <para> diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 0e80aeb65c..5937c0436a 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -36,6 +36,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" @@ -119,6 +120,9 @@ static void set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); +static RelOptInfo *partitionwise_make_rel_from_joinlist(PlannerInfo *root, + RelOptInfo *parent, + List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, pushdown_safety_info *safetyInfo); static bool recurse_pushdown_safe(Node *setOp, Query *topquery, @@ -181,13 +185,30 @@ make_one_rel(PlannerInfo *root, List *joinlist) /* * Generate access paths for the entire join tree. + * + * If we're doing this for an UPDATE or DELETE query whose target is a + * partitioned table, we must do the join planning against each of its + * leaf partitions instead. */ - rel = make_rel_from_joinlist(root, joinlist); + if (root->parse->resultRelation && + root->parse->commandType != CMD_INSERT && + root->simple_rel_array[root->parse->resultRelation] && + root->simple_rel_array[root->parse->resultRelation]->part_scheme) + { + RelOptInfo *rootrel = root->simple_rel_array[root->parse->resultRelation]; - /* - * The result should join all and only the query's base rels. - */ - Assert(bms_equal(rel->relids, root->all_baserels)); + rel = partitionwise_make_rel_from_joinlist(root, rootrel, joinlist); + } + else + { + rel = make_rel_from_joinlist(root, joinlist); + + /* + * The result should join all and only the query's base rels. + */ + Assert(bms_equal(rel->relids, root->all_baserels)); + + } return rel; } @@ -2591,6 +2612,72 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows) } /* + * partitionwise_make_rel_from_joinlist + * performs join planning against each of the leaf partitions contained + * in the partition tree whose root relation is 'parent' + * + * Recursively called for each partitioned table contained in a given + *partition tree. + */ +static RelOptInfo * +partitionwise_make_rel_from_joinlist(PlannerInfo *root, + RelOptInfo *parent, + List *joinlist) +{ + int i; + + Assert(root->parse->resultRelation != 0); + Assert(parent->part_scheme != NULL); + + for (i = 0; i < parent->nparts; i++) + { + RelOptInfo *partrel = parent->part_rels[i]; + AppendRelInfo *appinfo; + List *translated_joinlist; + List *saved_join_info_list = list_copy(root->join_info_list); + + /* Ignore pruned partitions. */ + if (IS_DUMMY_REL(partrel)) + continue; + + /* + * Hack to make the join planning code believe that 'partrel' can + * be joined against. + */ + partrel->reloptkind = RELOPT_BASEREL; + + /* + * Replace references to the parent rel in expressions relevant to join + * planning. + */ + appinfo = root->append_rel_array[partrel->relid]; + translated_joinlist = (List *) + adjust_appendrel_attrs(root, (Node *) joinlist, + 1, &appinfo); + root->join_info_list = (List *) + adjust_appendrel_attrs(root, + (Node *) root->join_info_list, + 1, &appinfo); + /* Reset join planning data structures for a new partition. */ + root->join_rel_list = NIL; + root->join_rel_hash = NULL; + + /* Recurse if the partition is itself a partitioned table. */ + if (partrel->part_scheme != NULL) + partrel = partitionwise_make_rel_from_joinlist(root, partrel, + translated_joinlist); + else + /* Perform the join planning and save the resulting relation. */ + parent->part_rels[i] = + make_rel_from_joinlist(root, translated_joinlist); + + root->join_info_list = saved_join_info_list; + } + + return parent; +} + +/* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. * diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index b05adc70c4..3f0d80eaa6 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -266,7 +266,9 @@ query_planner(PlannerInfo *root, List *tlist, /* Check that we got at least one usable path */ if (!final_rel || !final_rel->cheapest_total_path || - final_rel->cheapest_total_path->param_info != NULL) + final_rel->cheapest_total_path->param_info != NULL || + (final_rel->relid == root->parse->resultRelation && + root->parse->commandType == CMD_INSERT)) elog(ERROR, "failed to construct the join relation"); return final_rel; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 96bf0601a8..076dbd3d62 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -238,6 +238,16 @@ static bool group_by_has_partkey(RelOptInfo *input_rel, List *targetList, List *groupClause); +static void partitionwise_adjust_scanjoin_target(PlannerInfo *root, + RelOptInfo *parent, + List **partition_subroots, + List **partitioned_rels, + List **resultRelations, + List **subpaths, + List **WCOLists, + List **returningLists, + List **rowMarks); + /***************************************************************************** * @@ -959,7 +969,9 @@ subquery_planner(PlannerGlobal *glob, Query *parse, * needs special processing, else go straight to grouping_planner. */ if (parse->resultRelation && - rt_fetch(parse->resultRelation, parse->rtable)->inh) + rt_fetch(parse->resultRelation, parse->rtable)->inh && + rt_fetch(parse->resultRelation, parse->rtable)->relkind != + RELKIND_PARTITIONED_TABLE) inheritance_planner(root); else grouping_planner(root, false, tuple_fraction); @@ -1688,6 +1700,14 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, RelOptInfo *current_rel; RelOptInfo *final_rel; ListCell *lc; + List *orig_parse_tlist = list_copy(parse->targetList); + List *partition_subroots = NIL; + List *partitioned_rels = NIL; + List *partition_resultRelations = NIL; + List *partition_subpaths = NIL; + List *partition_WCOLists = NIL; + List *partition_returningLists = NIL; + List *partition_rowMarks = NIL; /* Tweak caller-supplied tuple_fraction if have LIMIT/OFFSET */ if (parse->limitCount || parse->limitOffset) @@ -2018,13 +2038,44 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, scanjoin_targets_contain_srfs = NIL; } - /* Apply scan/join target. */ - scanjoin_target_same_exprs = list_length(scanjoin_targets) == 1 - && equal(scanjoin_target->exprs, current_rel->reltarget->exprs); - apply_scanjoin_target_to_paths(root, current_rel, scanjoin_targets, - scanjoin_targets_contain_srfs, - scanjoin_target_parallel_safe, - scanjoin_target_same_exprs); + /* + * For an UPDATE/DELETE query whose target is partitioned table, we + * must generate the targetlist for each of its leaf partitions and + * apply that. + */ + if (current_rel->reloptkind == RELOPT_BASEREL && + current_rel->part_scheme && + current_rel->relid == root->parse->resultRelation && + parse->commandType != CMD_INSERT) + { + /* + * scanjoin_target shouldn't have changed from final_target, + * because UPDATE/DELETE doesn't support various features that + * would've required modifications that are performed above. + * That's important because we'll generate final_target freshly + * for each partition in partitionwise_adjust_scanjoin_target. + */ + Assert(scanjoin_target == final_target); + root->parse->targetList = orig_parse_tlist; + partitionwise_adjust_scanjoin_target(root, current_rel, + &partition_subroots, + &partitioned_rels, + &partition_resultRelations, + &partition_subpaths, + &partition_WCOLists, + &partition_returningLists, + &partition_rowMarks); + } + else + { + /* Apply scan/join target. */ + scanjoin_target_same_exprs = list_length(scanjoin_targets) == 1 + && equal(scanjoin_target->exprs, current_rel->reltarget->exprs); + apply_scanjoin_target_to_paths(root, current_rel, scanjoin_targets, + scanjoin_targets_contain_srfs, + scanjoin_target_parallel_safe, + scanjoin_target_same_exprs); + } /* * Save the various upper-rel PathTargets we just computed into @@ -2136,93 +2187,119 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, final_rel->useridiscurrent = current_rel->useridiscurrent; final_rel->fdwroutine = current_rel->fdwroutine; - /* - * Generate paths for the final_rel. Insert all surviving paths, with - * LockRows, Limit, and/or ModifyTable steps added if needed. - */ - foreach(lc, current_rel->pathlist) + if (current_rel->reloptkind == RELOPT_BASEREL && + current_rel->relid == root->parse->resultRelation && + current_rel->part_scheme && + parse->commandType != CMD_INSERT) { - Path *path = (Path *) lfirst(lc); - - /* - * If there is a FOR [KEY] UPDATE/SHARE clause, add the LockRows node. - * (Note: we intentionally test parse->rowMarks not root->rowMarks - * here. If there are only non-locking rowmarks, they should be - * handled by the ModifyTable node instead. However, root->rowMarks - * is what goes into the LockRows node.) - */ - if (parse->rowMarks) - { - path = (Path *) create_lockrows_path(root, final_rel, path, - root->rowMarks, - SS_assign_special_param(root)); - } - - /* - * If there is a LIMIT/OFFSET clause, add the LIMIT node. - */ - if (limit_needed(parse)) - { - path = (Path *) create_limit_path(root, final_rel, path, - parse->limitOffset, - parse->limitCount, - offset_est, count_est); - } - - /* - * If this is an INSERT/UPDATE/DELETE, and we're not being called from - * inheritance_planner, add the ModifyTable node. - */ - if (parse->commandType != CMD_SELECT && !inheritance_update) - { - List *withCheckOptionLists; - List *returningLists; - List *rowMarks; - - /* - * Set up the WITH CHECK OPTION and RETURNING lists-of-lists, if - * needed. - */ - if (parse->withCheckOptions) - withCheckOptionLists = list_make1(parse->withCheckOptions); - else - withCheckOptionLists = NIL; - - if (parse->returningList) - returningLists = list_make1(parse->returningList); - else - returningLists = NIL; - - /* - * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node - * will have dealt with fetching non-locked marked rows, else we - * need to have ModifyTable do that. - */ - if (parse->rowMarks) - rowMarks = NIL; - else - rowMarks = root->rowMarks; - - path = (Path *) + Path *path = (Path *) create_modifytable_path(root, final_rel, parse->commandType, parse->canSetTag, parse->resultRelation, - NIL, - false, - list_make1_int(parse->resultRelation), - list_make1(path), - list_make1(root), - withCheckOptionLists, - returningLists, - rowMarks, - parse->onConflict, + partitioned_rels, + root->partColsUpdated, + partition_resultRelations, + partition_subpaths, + partition_subroots, + partition_WCOLists, + partition_returningLists, + partition_rowMarks, + NULL, SS_assign_special_param(root)); - } - - /* And shove it into final_rel */ add_path(final_rel, path); } + else + { + /* + * Generate paths for the final_rel. Insert all surviving paths, with + * LockRows, Limit, and/or ModifyTable steps added if needed. + */ + foreach(lc, current_rel->pathlist) + { + Path *path = (Path *) lfirst(lc); + + /* + * If there is a FOR [KEY] UPDATE/SHARE clause, add the LockRows + * node. (Note: we intentionally test parse->rowMarks not + * root->rowMarks here. If there are only non-locking rowmarks, + * they should be handled by the ModifyTable node instead. + * However, root->rowMarks is what goes into the LockRows node.) + */ + if (parse->rowMarks) + { + path = (Path *) + create_lockrows_path(root, final_rel, path, + root->rowMarks, + SS_assign_special_param(root)); + } + + /* + * If there is a LIMIT/OFFSET clause, add the LIMIT node. + */ + if (limit_needed(parse)) + { + path = (Path *) create_limit_path(root, final_rel, path, + parse->limitOffset, + parse->limitCount, + offset_est, count_est); + } + + /* + * If this is an INSERT/UPDATE/DELETE, and we're not being called + * from inheritance_planner, add the ModifyTable node. + */ + if (parse->commandType != CMD_SELECT && !inheritance_update) + { + List *withCheckOptionLists; + List *returningLists; + List *rowMarks; + + /* + * Set up the WITH CHECK OPTION and RETURNING lists-of-lists, + * if needed. + */ + if (parse->withCheckOptions) + withCheckOptionLists = list_make1(parse->withCheckOptions); + else + withCheckOptionLists = NIL; + + if (parse->returningList) + returningLists = list_make1(parse->returningList); + else + returningLists = NIL; + + /* + * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows + * node will have dealt with fetching non-locked marked rows, + * else we need to have ModifyTable do that. + */ + if (parse->rowMarks) + rowMarks = NIL; + else + rowMarks = root->rowMarks; + + path = (Path *) + create_modifytable_path(root, final_rel, + parse->commandType, + parse->canSetTag, + parse->resultRelation, + NIL, + false, + list_make1_int(parse->resultRelation), + list_make1(path), + list_make1(root), + withCheckOptionLists, + returningLists, + rowMarks, + parse->onConflict, + SS_assign_special_param(root)); + } + + /* And shove it into final_rel */ + add_path(final_rel, path); + } + } /* * Generate partial paths for final_rel, too, if outer query levels might @@ -2259,6 +2336,129 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, } /* + * partitionwise_adjust_scanjoin_target + * adjusts query's targetlist for each partition in the partition tree + * whose root is 'parent' and apply it to their paths via + * apply_scanjoin_target_to_paths + * + * Its output also consists of various pieces of information that will go + * into the ModifyTable node that will be created for this query. + */ +static void +partitionwise_adjust_scanjoin_target(PlannerInfo *root, + RelOptInfo *parent, + List **subroots, + List **partitioned_rels, + List **resultRelations, + List **subpaths, + List **WCOLists, + List **returningLists, + List **rowMarks) +{ + Query *parse = root->parse; + int i; + + *partitioned_rels = lappend(*partitioned_rels, + list_make1_int(parent->relid)); + + for (i = 0; i < parent->nparts; i++) + { + RelOptInfo *child_rel = parent->part_rels[i]; + AppendRelInfo *appinfo; + int relid; + List *tlist; + PathTarget *scanjoin_target; + bool scanjoin_target_parallel_safe; + bool scanjoin_target_same_exprs; + PlannerInfo *partition_subroot; + Query *partition_parse; + + /* Ignore pruned partitions. */ + if (IS_DUMMY_REL(child_rel)) + continue; + + /* + * Extract the original relid of partition to fetch its AppendRelInfo. + * We must find it like this, because + * partitionwise_make_rel_from_joinlist replaces the original rel + * with one generated by join planning which may be different. + */ + relid = -1; + while ((relid = bms_next_member(child_rel->relids, relid)) > 0) + if (root->append_rel_array[relid] && + root->append_rel_array[relid]->parent_relid == + parent->relid) + break; + + appinfo = root->append_rel_array[relid]; + + /* Translate Query structure for this partition. */ + partition_parse = (Query *) + adjust_appendrel_attrs(root, + (Node *) parse, + 1, &appinfo); + + /* Recurse if partition is itself a partitioned table. */ + if (child_rel->part_scheme) + { + root->parse = partition_parse; + partitionwise_adjust_scanjoin_target(root, child_rel, + subroots, + partitioned_rels, + resultRelations, + subpaths, + WCOLists, + returningLists, + rowMarks); + /* Restore the Query for processing the next partition. */ + root->parse = parse; + } + else + { + /* + * Generate a separate PlannerInfo for this partition. We'll need + * it when generating the ModifyTable subplan for this partition. + */ + partition_subroot = makeNode(PlannerInfo); + *subroots = lappend(*subroots, partition_subroot); + memcpy(partition_subroot, root, sizeof(PlannerInfo)); + partition_subroot->parse = partition_parse; + + /* + * Preprocess the translated targetlist and save it in the + * partition's PlannerInfo for the perusal of later planning + * steps. + */ + tlist = preprocess_targetlist(partition_subroot); + partition_subroot->processed_tlist = tlist; + + /* Apply scan/join target. */ + scanjoin_target = create_pathtarget(root, tlist); + scanjoin_target_same_exprs = equal(scanjoin_target->exprs, + child_rel->reltarget->exprs); + scanjoin_target_parallel_safe = + is_parallel_safe(root, (Node *) scanjoin_target->exprs); + apply_scanjoin_target_to_paths(root, child_rel, + list_make1(scanjoin_target), + NIL, + scanjoin_target_parallel_safe, + scanjoin_target_same_exprs); + + /* Collect information that will go into the ModifyTable */ + *resultRelations = lappend_int(*resultRelations, relid); + *subpaths = lappend(*subpaths, child_rel->cheapest_total_path); + if (partition_parse->withCheckOptions) + *WCOLists = lappend(*WCOLists, partition_parse->withCheckOptions); + if (partition_parse->returningList) + *returningLists = lappend(*returningLists, + partition_parse->returningList); + if (partition_parse->rowMarks) + *rowMarks = lappend(*rowMarks, partition_parse->rowMarks); + } + } +} + +/* * Do preprocessing for groupingSets clause and related data. This handles the * preliminary steps of expanding the grouping sets, organizing them into lists * of rollups, and preparing annotations which will later be filled in with @@ -6964,7 +7164,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, } /* Build new paths for this relation by appending child paths. */ - if (live_children != NIL) + if (live_children != NIL && + !(rel->reloptkind == RELOPT_BASEREL && + rel->relid == root->parse->resultRelation)) add_paths_to_append_rel(root, rel, live_children); } diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 690b6bbab7..f4c485cdc9 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -2265,8 +2265,34 @@ adjust_appendrel_attrs_mutator(Node *node, context->appinfos); return (Node *) phv; } + + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 8369e3ad62..8d67f21f42 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1265,36 +1265,6 @@ get_relation_constraints(PlannerInfo *root, } } - /* - * Append partition predicates, if any. - * - * For selects, partition pruning uses the parent table's partition bound - * descriptor, instead of constraint exclusion which is driven by the - * individual partition's partition constraint. - */ - if (enable_partition_pruning && root->parse->commandType != CMD_SELECT) - { - List *pcqual = RelationGetPartitionQual(relation); - - if (pcqual) - { - /* - * Run the partition quals through const-simplification similar to - * check constraints. We skip canonicalize_qual, though, because - * partition quals should be in canonical form already; also, - * since the qual is in implicit-AND format, we'd have to - * explicitly convert it to explicit-AND format and back again. - */ - pcqual = (List *) eval_const_expressions(root, (Node *) pcqual); - - /* Fix Vars to have the desired varno */ - if (varno != 1) - ChangeVarNodes((Node *) pcqual, 1, varno, 0); - - result = list_concat(result, pcqual); - } - } - heap_close(relation, NoLock); return result; diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index 7d04d12c6e..9074182512 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1752,7 +1752,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_1 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_1 -> Nested Loop Semi Join @@ -1760,7 +1760,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_2 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_2 (28 rows) -- 2.11.0
0002-Lazy-creation-of-partition-objects-for-planning.patch (text/plain, 46.6K) download From bed30ca4b5ddd258a7593d24aeffd7db2a6e70c9 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 16 May 2018 14:35:40 +0900 Subject: [PATCH 2/3] Lazy creation of partition objects for planning With the current approach, *all* partitions are opened and range table entries are created for them in the planner's prep phase, which is much sooner than when partition pruning is performed. This means that query_planner ends up spending cycles and memory on many partitions that potentially won't be included in the plan, such as creating RelOptInfos, AppendRelInfos. To avoid that, add partition range table entries and other planning data structures for only partitions that remain after applying partition pruning. Some code like that of partitionwise join rely on the fact that even though partitions may have been pruned, they would still have a RelOptInfo, albeit marked dummy to handle the outer join case where the pruned partition appears on the nullable side of join. So this commit also teaches the partitionwise join code to allocate dummy RelOptInfos for pruned partitions. There are couple of regression test diffs caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child and also that the individual partition RT entries are now created in the order in which their parent's are processed whereas previously they'd be added to the range table in the order of depth-first expansion of the tree. --- src/backend/optimizer/path/allpaths.c | 60 +++-- src/backend/optimizer/path/joinrels.c | 5 + src/backend/optimizer/plan/initsplan.c | 60 +++++ src/backend/optimizer/plan/planmain.c | 30 --- src/backend/optimizer/plan/planner.c | 8 +- src/backend/optimizer/prep/prepunion.c | 314 +++++++++------------- src/backend/optimizer/util/plancat.c | 12 +- src/backend/optimizer/util/relnode.c | 169 ++++++++++-- src/backend/partitioning/partprune.c | 100 ++++--- src/include/nodes/relation.h | 4 + src/include/optimizer/pathnode.h | 6 + src/include/optimizer/plancat.h | 2 +- src/include/optimizer/planmain.h | 3 + src/include/optimizer/prep.h | 10 + src/include/partitioning/partprune.h | 2 +- src/test/regress/expected/join.out | 22 +- src/test/regress/expected/partition_aggregate.out | 4 +- 17 files changed, 486 insertions(+), 325 deletions(-) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 5937c0436a..d6d1e26209 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -151,6 +151,7 @@ make_one_rel(PlannerInfo *root, List *joinlist) { RelOptInfo *rel; Index rti; + double total_pages; /* * Construct the all_baserels Relids set. @@ -181,6 +182,35 @@ make_one_rel(PlannerInfo *root, List *joinlist) * then generate access paths. */ set_base_rel_sizes(root); + + /* + * We should now have size estimates for every actual table involved in + * the query, and we also know which if any have been deleted from the + * query by join removal; so we can compute total_table_pages. + * + * Note that appendrels are not double-counted here, even though we don't + * bother to distinguish RelOptInfos for appendrel parents, because the + * parents will still have size zero. + * + * XXX if a table is self-joined, we will count it once per appearance, + * which perhaps is the wrong thing ... but that's not completely clear, + * and detecting self-joins here is difficult, so ignore it for now. + */ + total_pages = 0; + for (rti = 1; rti < root->simple_rel_array_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + + if (brel == NULL) + continue; + + Assert(brel->relid == rti); /* sanity check on array */ + + if (IS_SIMPLE_REL(brel)) + total_pages += (double) brel->pages; + } + root->total_table_pages = total_pages; + set_base_rel_pathlists(root); /* @@ -896,8 +926,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -913,21 +941,14 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, * partitioned table's list will contain all such indexes. */ if (rte->relkind == RELKIND_PARTITIONED_TABLE) + { rel->partitioned_child_rels = list_make1_int(rti); - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; + /* + * And do prunin. Note that this adds AppendRelInfo's of only the + * partitions that are not pruned. + */ + prune_append_rel_partitions(root, rel); } /* @@ -1178,13 +1199,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, continue; } - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - if (relation_excluded_by_constraints(root, childrel, childRTE)) { /* @@ -2637,7 +2651,7 @@ partitionwise_make_rel_from_joinlist(PlannerInfo *root, List *saved_join_info_list = list_copy(root->join_info_list); /* Ignore pruned partitions. */ - if (IS_DUMMY_REL(partrel)) + if (partrel == NULL || IS_DUMMY_REL(partrel)) continue; /* diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 7008e1318e..af9c4ac8fd 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1369,6 +1369,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts); + if (child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts); + /* We should never try to join two overlapping sets of rels. */ Assert(!bms_overlap(child_rel1->relids, child_rel2->relids)); child_joinrelids = bms_union(child_rel1->relids, child_rel2->relids); diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 01335db511..d85f782d50 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -132,6 +132,66 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode) (int) nodeTag(jtnode)); } +/* + * add_rel_partitions_to_query + * create range table entries and "otherrel" RelOptInfos and for the + * partitions of 'rel' specified by the caller + * + * To store the objects thus created, various arrays in 'root' are expanded + * by repalloc'ing them. + */ +void +add_rel_partitions_to_query(PlannerInfo *root, RelOptInfo *rel, + bool scan_all_parts, + Bitmapset *partindexes) +{ + int new_size; + int num_added_parts; + int i; + + Assert(partindexes != NULL || scan_all_parts); + + /* Expand the PlannerInfo arrays to hold new partition objects. */ + num_added_parts = scan_all_parts ? rel->nparts : + bms_num_members(partindexes); + new_size = root->simple_rel_array_size + num_added_parts; + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + if (root->append_rel_array) + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + else + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + + /* Set the contents of just allocated memory to 0. */ + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * num_added_parts); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * num_added_parts); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * num_added_parts); + root->simple_rel_array_size = new_size; + + /* And add the partitions. */ + if (scan_all_parts) + for (i = 0; i < rel->nparts; i++) + rel->part_rels[i] = build_partition_rel(root, rel, + rel->part_oids[i]); + else + { + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) + rel->part_rels[i] = build_partition_rel(root, rel, + rel->part_oids[i]); + } +} /***************************************************************************** * diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 3f0d80eaa6..1bd3f0e350 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -57,8 +57,6 @@ query_planner(PlannerInfo *root, List *tlist, Query *parse = root->parse; List *joinlist; RelOptInfo *final_rel; - Index rti; - double total_pages; /* * If the query has an empty join tree, then it's something easy like @@ -232,34 +230,6 @@ query_planner(PlannerInfo *root, List *tlist, extract_restriction_or_clauses(root); /* - * We should now have size estimates for every actual table involved in - * the query, and we also know which if any have been deleted from the - * query by join removal; so we can compute total_table_pages. - * - * Note that appendrels are not double-counted here, even though we don't - * bother to distinguish RelOptInfos for appendrel parents, because the - * parents will still have size zero. - * - * XXX if a table is self-joined, we will count it once per appearance, - * which perhaps is the wrong thing ... but that's not completely clear, - * and detecting self-joins here is difficult, so ignore it for now. - */ - total_pages = 0; - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - - if (brel == NULL) - continue; - - Assert(brel->relid == rti); /* sanity check on array */ - - if (IS_SIMPLE_REL(brel)) - total_pages += (double) brel->pages; - } - root->total_table_pages = total_pages; - - /* * Ready to do the primary planning. */ final_rel = make_one_rel(root, joinlist); diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 076dbd3d62..88db46a6e5 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -2374,7 +2374,7 @@ partitionwise_adjust_scanjoin_target(PlannerInfo *root, Query *partition_parse; /* Ignore pruned partitions. */ - if (IS_DUMMY_REL(child_rel)) + if (child_rel == NULL || IS_DUMMY_REL(child_rel)) continue; /* @@ -7134,6 +7134,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -7237,6 +7240,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index f4c485cdc9..279f686fb0 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -49,6 +49,8 @@ #include "parser/parse_coerce.h" #include "parser/parsetree.h" #include "utils/lsyscache.h" +#include "utils/lsyscache.h" +#include "utils/partcache.h" #include "utils/rel.h" #include "utils/selfuncs.h" #include "utils/syscache.h" @@ -101,21 +103,10 @@ static List *generate_append_tlist(List *colTypes, List *colCollations, static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist); static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti); -static void expand_partitioned_rtentry(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); -static void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + RangeTblEntry *oldrte, RangeTblEntry *newrte, + Index newvarno, List **translated_vars); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, @@ -1522,6 +1513,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) LOCKMODE lockmode; List *inhOIDs; ListCell *l; + List *appinfos = NIL; /* Does RT entry allow inheritance? */ if (!rte->inh) @@ -1585,173 +1577,58 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) if (oldrc) oldrc->isParent = true; + /* Partitioned tables are expanded elsewhere. */ + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + { + list_free(inhOIDs); + return; + } + /* * Must open the parent relation to examine its tupdesc. We need not lock * it; we assume the rewriter already did. */ oldrelation = heap_open(parentOID, NoLock); - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) + foreach(l, inhOIDs) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); + Oid childOID = lfirst_oid(l); + Index childRTindex = 0; + RangeTblEntry *childrte = NULL; + AppendRelInfo *appinfo = NULL; - /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. - */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); + add_inheritance_child_to_query(root, rte, rti, + oldrelation->rd_rel->reltype, + RelationGetDescr(oldrelation), + oldrc, childOID, NoLock, + &appinfo, &childrte, + &childRTindex); + Assert(childRTindex > 1); + Assert(childrte != NULL); + Assert(appinfo != NULL); + appinfos = lappend(appinfos, appinfo); } + + /* + * If all the children were temp tables, pretend it's a + * non-inheritance situation; we don't need Append node in that case. + * The duplicate RTE we added for the parent table is harmless, so we + * don't bother to get rid of it; ditto for the useless PlanRowMark + * node. + */ + if (list_length(appinfos) < 2) + rte->inh = false; else - { - List *appinfos = NIL; - RangeTblEntry *childrte; - Index childRTindex; - - /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. - */ - foreach(l, inhOIDs) - { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = heap_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - heap_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - heap_close(newrelation, NoLock); - } - - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); - - } + root->append_rel_list = list_concat(root->append_rel_list, + appinfos); heap_close(oldrelation, NoLock); } /* - * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. - * - * Note that RelationGetPartitionDispatchInfo will expand partitions in the - * same order as this code. - */ -static void -expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) -{ - int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); - - check_stack_depth(); - - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); - - /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. - */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); - - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); - - /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. - */ - if (partdesc->nparts == 0) - { - parentrte->inh = false; - return; - } - - for (i = 0; i < partdesc->nparts; i++) - { - Oid childOID = partdesc->oids[i]; - Relation childrel; - - /* Open rel; we already have required locks */ - childrel = heap_open(childOID, NoLock); - - /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. - */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); - - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); - - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); - - /* Close child relation, but keep locks */ - heap_close(childrel, NoLock); - } -} - -/* - * expand_single_inheritance_child + * add_inheritance_child_to_query * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. + * maybe a PlanRowMark for a child relation. * * We now expand the partition hierarchy level by level, creating a * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each @@ -1769,19 +1646,70 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, * The child RangeTblEntry and its RTI are returned in "childrte_p" and * "childRTindex_p" resp. */ -static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +void +add_inheritance_child_to_query(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, Oid parentRelType, + TupleDesc parentDesc, + PlanRowMark *top_parentrc, + Oid childOID, int lockmode, + AppendRelInfo **appinfo_p, + RangeTblEntry **childrte_p, + Index *childRTindex_p) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); - Oid childOID = RelationGetRelid(childrel); + Oid parentOID = parentrte->relid; RangeTblEntry *childrte; Index childRTindex; AppendRelInfo *appinfo; + Relation childrel = NULL; + char child_relkind; + Oid child_reltype; + TupleDesc childDesc; + + *appinfo_p = NULL; + *childrte_p = NULL; + *childRTindex_p = 0; + + /* Open rel if needed; we already have required locks */ + if (childOID != parentOID) + { + childrel = heap_open(childOID, lockmode); + + /* + * Temporary partitions belonging to other sessions should have been + * disallowed at definition, but for paranoia's sake, let's double + * check. + */ + if (RELATION_IS_OTHER_TEMP(childrel)) + { + if (childrel->rd_rel->relispartition) + elog(ERROR, "temporary relation from another session found as partition"); + heap_close(childrel, lockmode); + return; + } + + child_relkind = childrel->rd_rel->relkind; + + /* + * No point in adding to the query a partitioned table that has no + * partitions. + */ + if (child_relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(childrel)->nparts == 0) + { + heap_close(childrel, lockmode); + return; + } + + child_reltype = childrel->rd_rel->reltype; + childDesc = RelationGetDescr(childrel); + } + else + { + child_relkind = parentrte->relkind; + child_reltype = parentRelType; + childDesc = parentDesc; + } /* * Build an RTE for the child, and attach to query's rangetable list. We @@ -1798,7 +1726,7 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, childrte = copyObject(parentrte); *childrte_p = childrte; childrte->relid = childOID; - childrte->relkind = childrel->rd_rel->relkind; + childrte->relkind = child_relkind; /* A partitioned child will need to be expanded further. */ if (childOID != parentOID && childrte->relkind == RELKIND_PARTITIONED_TABLE) @@ -1823,12 +1751,13 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, appinfo = makeNode(AppendRelInfo); appinfo->parent_relid = parentRTindex; appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->rd_rel->reltype; - appinfo->child_reltype = childrel->rd_rel->reltype; - make_inh_translation_list(parentrel, childrel, childRTindex, + appinfo->parent_reltype = parentRelType; + appinfo->child_reltype = child_reltype; + make_inh_translation_list(parentDesc, childDesc, + parentrte, childrte, childRTindex, &appinfo->translated_vars); appinfo->parent_reloid = parentOID; - *appinfos = lappend(*appinfos, appinfo); + *appinfo_p = appinfo; /* * Translate the column permissions bitmaps to the child's attnums (we @@ -1879,6 +1808,13 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } + + /* Close child relations, but keep locks */ + if (childOID != parentOID) + { + Assert(childrel != NULL); + heap_close(childrel, lockmode); + } } /* @@ -1889,14 +1825,12 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, * For paranoia's sake, we match type/collation as well as attribute name. */ static void -make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, - List **translated_vars) +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + RangeTblEntry *oldrte, RangeTblEntry *newrte, + Index newvarno, List **translated_vars) { List *vars = NIL; - TupleDesc old_tupdesc = RelationGetDescr(oldrelation); - TupleDesc new_tupdesc = RelationGetDescr(newrelation); - Oid new_relid = RelationGetRelid(newrelation); + Oid new_relid = newrte->relid; int oldnatts = old_tupdesc->natts; int newnatts = new_tupdesc->natts; int old_attno; @@ -1926,7 +1860,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, * When we are generating the "translation list" for the parent table * of an inheritance set, no need to search for matches. */ - if (oldrelation == newrelation) + if (oldrte->relid == newrte->relid) { vars = lappend(vars, makeVar(newvarno, (AttrNumber) (old_attno + 1), @@ -1955,7 +1889,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, newtup = SearchSysCacheAttName(new_relid, attname); if (!newtup) elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(newrte->relid)); new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; ReleaseSysCache(newtup); @@ -1965,10 +1899,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, /* Found it, check type and collation match */ if (atttypid != att->atttypid || atttypmod != att->atttypmod) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(newrte->relid)); if (attcollation != att->attcollation) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(newrte->relid)); vars = lappend(vars, makeVar(newvarno, (AttrNumber) (new_attno + 1), @@ -2121,7 +2055,7 @@ adjust_appendrel_attrs_mutator(Node *node, } } - if (var->varlevelsup == 0 && appinfo) + if (var->varlevelsup == 0 && appinfo && appinfo->translated_vars) { var->varno = appinfo->child_relid; var->varnoold = appinfo->child_relid; diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 8d67f21f42..100dfd8e0c 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -106,7 +106,7 @@ static void set_baserel_partition_key_exprs(Relation relation, */ void get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) + Bitmapset *updatedCols, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; @@ -449,7 +449,15 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * inheritance parents may be partitioned. */ if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { set_relation_partition_info(root, rel, relation); + if (!root->partColsUpdated) + root->partColsUpdated = + has_partition_attrs(relation, updatedCols, NULL); + } + + rel->tupdesc = RelationGetDescr(relation); + rel->reltype = RelationGetForm(relation)->reltype; heap_close(relation, NoLock); @@ -1883,6 +1891,8 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, rel->nparts = partdesc->nparts; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); + rel->part_oids = (Oid *) palloc(rel->nparts * sizeof(Oid)); + memcpy(rel->part_oids, partdesc->oids, rel->nparts * sizeof(Oid)); } /* diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index c69740eda6..b267f07c18 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -16,6 +16,7 @@ #include <limits.h> +#include "catalog/pg_class.h" #include "miscadmin.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" @@ -27,6 +28,7 @@ #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" #include "partitioning/partbounds.h" +#include "storage/lockdefs.h" #include "utils/hsearch.h" @@ -137,6 +139,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) /* Rel should not exist already */ Assert(relid > 0 && relid < root->simple_rel_array_size); + if (root->simple_rel_array[relid] != NULL) elog(ERROR, "rel %d already exists", relid); @@ -218,7 +221,8 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte->relid, rte->inh, rte->updatedCols, + rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -268,41 +272,30 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) if (rte->inh) { ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - if (nparts > 0) + /* + * For partitioned tables, we just allocate space for RelOptInfo's. + * pointers for all partitions and copy the partition OIDs from the + * relcache. Actual RelOptInfo is built for a partition only if it is + * not pruned. + */ + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + { rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); + palloc0(sizeof(RelOptInfo *) * rel->nparts); + return rel; + } foreach(l, root->append_rel_list) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != relid) continue; - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; + (void) build_simple_rel(root, appinfo->child_relid, rel); } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); } return rel; @@ -1768,3 +1761,131 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->nullable_partexprs[cnt] = nullable_partexpr; } } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx) +{ + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (noop) AppendRelInfo for parent, because we're setting + * the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + AppendRelInfo *appinfo = makeNode(AppendRelInfo); + + appinfo->parent_relid = parent->relid; + appinfo->child_relid = parent->relid; + appinfo->parent_reltype = parent->reltype; + appinfo->child_reltype = parent->reltype; + /* leaving translated_vars to NIL to mean no translation needed */ + appinfo->parent_reloid = root->simple_rte_array[parent->relid]->relid; + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} + +/* + * build_partition_rel + * This adds a valid partition to the query by adding it to the + * range table and creating planner data structures for it + */ +RelOptInfo * +build_partition_rel(PlannerInfo *root, RelOptInfo *parent, Oid partoid) +{ + RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; + RelOptInfo *result; + Index partRTindex = 0; + RangeTblEntry *partrte = NULL; + AppendRelInfo *appinfo = NULL; + PlanRowMark *rootrc = NULL; + + /* Locate the root partitioned table and fetch its PlanRowMark, if any. */ + if (root->rowMarks) + { + Index rootRTindex = 0; + + /* + * The root partitioned table itself might be a child of UNION ALL + * parent, so we must resort to finding the root parent like this. + */ + rootRTindex = parent->relid; + if (root->append_rel_array[rootRTindex]) + { + AppendRelInfo *tmp = root->append_rel_array[rootRTindex]; + + /* + * Keep moving up until we each the parent rel that's not a + * partitioned table. The one before that one would be the root + * parent. + */ + while(root->simple_rel_array[rootRTindex]->part_scheme) + { + tmp = root->append_rel_array[tmp->parent_relid]; + if (tmp == NULL) + break; + rootRTindex = tmp->parent_relid; + } + } + + rootrc = get_plan_rowmark(root->rowMarks, rootRTindex); + } + + /* + * expand_inherited_rtentry alreay locked all partitions, so pass + * NoLock for lockmode. + */ + add_inheritance_child_to_query(root, parentrte, parent->relid, + parent->reltype, parent->tupdesc, + rootrc, partoid, NoLock, + &appinfo, &partrte, &partRTindex); + + /* Partition turned out to be a partitioned table with 0 partitions. */ + if (partrte == NULL) + return NULL; + + Assert(appinfo != NULL); + root->append_rel_list = lappend(root->append_rel_list, appinfo); + root->simple_rte_array[partRTindex] = partrte; + root->append_rel_array[partRTindex] = appinfo; + + /* Build the RelOptInfo. */ + result = build_simple_rel(root, partRTindex, parent); + + /* Set the information created by create_lateral_join_info(). */ + result->direct_lateral_relids = parent->direct_lateral_relids; + result->lateral_relids = parent->lateral_relids; + result->lateral_referencers = parent->lateral_referencers; + + return result; +} diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index b5c1c7d4dd..331e2717b2 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -45,7 +45,9 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/pathnode.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/predtest.h" #include "optimizer/prep.h" @@ -443,9 +445,18 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; + if (partrel == NULL) + { + subplan_map[i] = -1; + subpart_map[i] = -1; + continue; + } + + subplanidx = relid_subplan_map[partrel->relid] - 1; + subpartidx = relid_subpart_map[partrel->relid] - 1; subplan_map[i] = subplanidx; subpart_map[i] = subpartidx; if (subplanidx >= 0) @@ -548,61 +559,68 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) * * Callers must ensure that 'rel' is a partitioned table. */ -Relids -prune_append_rel_partitions(RelOptInfo *rel) +void +prune_append_rel_partitions(PlannerInfo *root, RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; - bool contradictory; + bool contradictory, + scan_all_parts = false; PartitionPruneContext context; - Bitmapset *partindexes; - int i; + Bitmapset *partindexes = NULL; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ if (rel->nparts == 0) - return NULL; + return; - /* - * Process clauses. If the clauses are found to be contradictory, we can - * return the empty set. - */ - pruning_steps = gen_partprune_steps(rel, clauses, &contradictory); - if (contradictory) - return NULL; - - /* Set up PartitionPruneContext */ - context.strategy = rel->part_scheme->strategy; - context.partnatts = rel->part_scheme->partnatts; - context.nparts = rel->nparts; - context.boundinfo = rel->boundinfo; - context.partcollation = rel->part_scheme->partcollation; - context.partsupfunc = rel->part_scheme->partsupfunc; - context.stepcmpfuncs = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * + if (enable_partition_pruning && clauses != NIL) + { + /* + * Process clauses. If the clauses are found to be contradictory, we + * can return the empty set. + */ + pruning_steps = gen_partprune_steps(rel, clauses, &contradictory); + if (!contradictory) + { + context.strategy = rel->part_scheme->strategy; + context.partnatts = rel->part_scheme->partnatts; + context.nparts = rel->nparts; + context.boundinfo = rel->boundinfo; + context.partcollation = rel->part_scheme->partcollation; + context.partsupfunc = rel->part_scheme->partsupfunc; + context.stepcmpfuncs = (FmgrInfo *) + palloc0(sizeof(FmgrInfo) * context.partnatts * list_length(pruning_steps)); - context.ppccontext = CurrentMemoryContext; + context.ppccontext = CurrentMemoryContext; - /* These are not valid when being called from the planner */ - context.partrel = NULL; - context.planstate = NULL; - context.exprstates = NULL; - context.exprhasexecparam = NULL; - context.evalexecparams = false; + /* These are not valid when being called from the planner */ + context.partrel = NULL; + context.planstate = NULL; + context.exprstates = NULL; + context.exprhasexecparam = NULL; + context.evalexecparams = false; - /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); + /* Actual pruning happens here. */ + partindexes = get_matching_partitions(&context, pruning_steps); - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); + /* No need to add partitions if all were pruned. */ + if (bms_is_empty(partindexes)) + return; + } + else + scan_all_parts = true; + } + else + scan_all_parts = true; - return result; + /* + * Build selected partitions' range table entries, RelOptInfos, and + * AppendRelInfos. + */ + add_rel_partitions_to_query(root, rel, scan_all_parts, partindexes); } /* diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 41caf873fb..1e8371d814 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -15,6 +15,7 @@ #define RELATION_H #include "access/sdir.h" +#include "access/tupdesc.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/params.h" @@ -695,11 +696,14 @@ typedef struct RelOptInfo int nparts; /* number of partitions */ struct PartitionBoundInfoData *boundinfo; /* Partition bounds */ List *partition_qual; /* partition constraint */ + Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ + TupleDesc tupdesc; + Oid reltype; } RelOptInfo; /* diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 7c5ff22650..4f567765a4 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -297,5 +297,11 @@ extern RelOptInfo *build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel, RelOptInfo *inner_rel, RelOptInfo *parent_joinrel, List *restrictlist, SpecialJoinInfo *sjinfo, JoinType jointype); +extern RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + int partidx); +extern RelOptInfo *build_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + Oid partoid); #endif /* PATHNODE_H */ diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index 7d53cbbb87..edaf2a3b4f 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -26,7 +26,7 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); + bool inhparent, Bitmapset *updatedCols, RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index c8ab0280d2..1916a33467 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -73,6 +73,9 @@ extern int from_collapse_limit; extern int join_collapse_limit; extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode); +extern void add_rel_partitions_to_query(PlannerInfo *root, RelOptInfo *rel, + bool scan_all_parts, + Bitmapset *partindexes); extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist); extern void add_vars_to_targetlist(PlannerInfo *root, List *vars, Relids where_needed, bool create_new_ph); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 38608770a2..ca66f75544 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -49,6 +49,16 @@ extern RelOptInfo *plan_set_operations(PlannerInfo *root); extern void expand_inherited_tables(PlannerInfo *root); +extern void add_inheritance_child_to_query(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, Oid parentRelType, + TupleDesc parentDesc, + PlanRowMark *top_parentrc, + Oid childOID, int lockmode, + AppendRelInfo **appinfo_p, + RangeTblEntry **childrte_p, + Index *childRTindex_p); + extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h index b95c346bab..55a324583b 100644 --- a/src/include/partitioning/partprune.h +++ b/src/include/partitioning/partprune.h @@ -79,7 +79,7 @@ extern PartitionPruneInfo *make_partition_pruneinfo(PlannerInfo *root, List *subpaths, List *partitioned_rels, List *prunequal); -extern Relids prune_append_rel_partitions(RelOptInfo *rel); +extern void prune_append_rel_partitions(PlannerInfo *root, RelOptInfo *rel); extern Bitmapset *get_matching_partitions(PartitionPruneContext *context, List *pruning_steps); diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index dc6262be43..5f931591a6 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -5533,29 +5533,29 @@ select t1.b, ss.phv from join_ut1 t1 left join lateral (select t2.a as t2a, t3.a t3a, least(t1.a, t2.a, t3.a) phv from join_pt1 t2 join join_ut1 t3 on t2.a = t3.b) ss on t1.a = ss.t2a order by t1.a; - QUERY PLAN ------------------------------------------------------------------- + QUERY PLAN +-------------------------------------------------------------------- Sort - Output: t1.b, (LEAST(t1.a, t2.a, t3.a)), t1.a + Output: t1.b, (LEAST(t1.a, t2_1.a, t3.a)), t1.a Sort Key: t1.a -> Nested Loop Left Join - Output: t1.b, (LEAST(t1.a, t2.a, t3.a)), t1.a + Output: t1.b, (LEAST(t1.a, t2_1.a, t3.a)), t1.a -> Seq Scan on public.join_ut1 t1 Output: t1.a, t1.b, t1.c -> Hash Join - Output: t2.a, LEAST(t1.a, t2.a, t3.a) - Hash Cond: (t3.b = t2.a) + Output: t2_1.a, LEAST(t1.a, t2_1.a, t3.a) + Hash Cond: (t3.b = t2_1.a) -> Seq Scan on public.join_ut1 t3 Output: t3.a, t3.b, t3.c -> Hash - Output: t2.a + Output: t2_1.a -> Append - -> Seq Scan on public.join_pt1p1p1 t2 - Output: t2.a - Filter: (t1.a = t2.a) - -> Seq Scan on public.join_pt1p2 t2_1 + -> Seq Scan on public.join_pt1p1p1 t2_1 Output: t2_1.a Filter: (t1.a = t2_1.a) + -> Seq Scan on public.join_pt1p2 t2 + Output: t2.a + Filter: (t1.a = t2.a) (21 rows) select t1.b, ss.phv from join_ut1 t1 left join lateral diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index d286050c9a..d1ce6ad423 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) -- 2.11.0
0003-Only-lock-partitions-that-will-be-scanned-by-a-query.patch (text/plain, 3.3K) download From 57b8cadddce13952a0a62d37c51dd02c7a436ebc Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Thu, 23 Aug 2018 17:30:18 +0900 Subject: [PATCH 3/3] Only lock partitions that will be scanned by a query --- src/backend/optimizer/prep/prepunion.c | 8 +++----- src/backend/optimizer/util/relnode.c | 17 ++++++++++------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 279f686fb0..6a2adb5f4d 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -1555,14 +1555,15 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) lockmode = AccessShareLock; /* Scan for all members of inheritance set, acquire needed locks */ - inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); + if (rte->relkind != RELKIND_PARTITIONED_TABLE) + inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); /* * Check that there's at least one descendant, else treat as no-child * case. This could happen despite above has_subclass() check, if table * once had a child but no longer does. */ - if (list_length(inhOIDs) < 2) + if (rte->relkind != RELKIND_PARTITIONED_TABLE && list_length(inhOIDs) < 2) { /* Clear flag before returning */ rte->inh = false; @@ -1579,10 +1580,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) /* Partitioned tables are expanded elsewhere. */ if (rte->relkind == RELKIND_PARTITIONED_TABLE) - { - list_free(inhOIDs); return; - } /* * Must open the parent relation to examine its tupdesc. We need not lock diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index b267f07c18..f9bde0c058 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1825,16 +1825,16 @@ build_partition_rel(PlannerInfo *root, RelOptInfo *parent, Oid partoid) { RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; RelOptInfo *result; + Index rootRTindex = 0; Index partRTindex = 0; RangeTblEntry *partrte = NULL; AppendRelInfo *appinfo = NULL; PlanRowMark *rootrc = NULL; + int lockmode; /* Locate the root partitioned table and fetch its PlanRowMark, if any. */ if (root->rowMarks) { - Index rootRTindex = 0; - /* * The root partitioned table itself might be a child of UNION ALL * parent, so we must resort to finding the root parent like this. @@ -1861,13 +1861,16 @@ build_partition_rel(PlannerInfo *root, RelOptInfo *parent, Oid partoid) rootrc = get_plan_rowmark(root->rowMarks, rootRTindex); } - /* - * expand_inherited_rtentry alreay locked all partitions, so pass - * NoLock for lockmode. - */ + /* Determine the correct lockmode to use. */ + if (rootRTindex == root->parse->resultRelation) + lockmode = RowExclusiveLock; + else if (rootrc && RowMarkRequiresRowShareLock(rootrc->markType)) + lockmode = RowShareLock; + else + lockmode = AccessShareLock; add_inheritance_child_to_query(root, parentrte, parent->relid, parent->reltype, parent->tupdesc, - rootrc, partoid, NoLock, + rootrc, partoid, lockmode, &appinfo, &partrte, &partRTindex); /* Partition turned out to be a partitioned table with 0 partitions. */ -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-08-29 22:27  David Rowley <david.rowley@2ndquadrant.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  6 siblings, 1 reply; 265+ messages in thread

From: David Rowley @ 2018-08-29 22:27 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers

On 30 August 2018 at 00:06, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
> nparts  master    0001   0002   0003
> ======  ======    ====   ====   ====
> 0         2856    2893   2862   2816
> 8          507    1115   1447   1872
> 16         260     765   1173   1892
> 32         119     483    922   1884
> 64          59     282    615   1881
> 128         29     153    378   1835
> 256         14      79    210   1803
> 512          5      40    113   1728
> 1024         2      17     57   1616
> 2048         0*      9     30   1471
> 4096         0+      4     15   1236
> 8192         0=      2      7    975

Those look promising. Are you going to submit these patches to the
September 'fest?


-- 
 David Rowley                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2018-08-30 01:09  Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  6 siblings, 1 reply; 265+ messages in thread

From: Tsunakawa, Takayuki @ 2018-08-30 01:09 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers

From: Amit Langote [mailto:Langote_Amit_f8@lab.ntt.co.jp]
> I measured the gain in performance due to each patch on a modest virtual
> machine.  Details of the measurement and results follow.

Amazing!

UPDATE
> nparts  master    0001   0002   0003
> ======  ======    ====   ====   ====
> 0         2856    2893   2862   2816
> 8          507    1115   1447   1872

SELECT
> nparts  master    0001   0002   0003
> ======  ======    ====   ====   ====
> 0         2290    2329   2319   2268
> 8         1058    1077   1414   1788

Even a small number of partitions still introduces a not-small overhead (UPDATE:34%, SELECT:22%).  Do you think this overhead can be reduced further?  What part do you guess would be relevant?  This one?


> that it is now performed after pruning. However, it doesn't do anything
> about the fact that partitions are all still locked in the earlier phase.


Regards
Takayuki Tsunakawa



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-08-30 01:10  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: David Rowley <david.rowley@2ndquadrant.com>
  0 siblings, 0 replies; 265+ messages in thread

From: Amit Langote @ 2018-08-30 01:10 UTC (permalink / raw)
  To: David Rowley <david.rowley@2ndquadrant.com>; +Cc: pgsql-hackers

On 2018/08/30 7:27, David Rowley wrote:
> On 30 August 2018 at 00:06, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> nparts  master    0001   0002   0003
>> ======  ======    ====   ====   ====
>> 0         2856    2893   2862   2816
>> 8          507    1115   1447   1872
>> 16         260     765   1173   1892
>> 32         119     483    922   1884
>> 64          59     282    615   1881
>> 128         29     153    378   1835
>> 256         14      79    210   1803
>> 512          5      40    113   1728
>> 1024         2      17     57   1616
>> 2048         0*      9     30   1471
>> 4096         0+      4     15   1236
>> 8192         0=      2      7    975
> 
> Those look promising. Are you going to submit these patches to the
> September 'fest?

Thanks, I just did.

https://commitfest.postgresql.org/19/1778/

Regards,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-08-30 01:33  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
  0 siblings, 1 reply; 265+ messages in thread

From: Amit Langote @ 2018-08-30 01:33 UTC (permalink / raw)
  To: Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>; pgsql-hackers

On 2018/08/30 10:09, Tsunakawa, Takayuki wrote:
> From: Amit Langote [mailto:Langote_Amit_f8@lab.ntt.co.jp]
>> I measured the gain in performance due to each patch on a modest virtual
>> machine.  Details of the measurement and results follow.
> 
> Amazing!

Thanks.

> UPDATE
>> nparts  master    0001   0002   0003
>> ======  ======    ====   ====   ====
>> 0         2856    2893   2862   2816
>> 8          507    1115   1447   1872
> 
> SELECT
>> nparts  master    0001   0002   0003
>> ======  ======    ====   ====   ====
>> 0         2290    2329   2319   2268
>> 8         1058    1077   1414   1788
> 
> Even a small number of partitions still introduces a not-small overhead (UPDATE:34%, SELECT:22%).

Yeah, that's true.

> Do you think this overhead can be reduced further?

We can definitely try, but I'm not immediately sure if the further
improvements will come from continuing to fix the planner.  Maybe, the
overhead of partitioning could be attributed to other parts of the system.

> What part do you guess would be relevant?  This one?
> 
>> that it is now performed after pruning. However, it doesn't do anything
>> about the fact that partitions are all still locked in the earlier phase.

Actually, I wrote that for patch 0002.  The next patch (0003) is meant to
fix that.  So, the overhead you're seeing is even after making sure that
only the selected partitions are locked.

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2018-08-30 01:45  Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 0 replies; 265+ messages in thread

From: Tsunakawa, Takayuki @ 2018-08-30 01:45 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers

From: Amit Langote [mailto:Langote_Amit_f8@lab.ntt.co.jp]
> We can definitely try, but I'm not immediately sure if the further
> improvements will come from continuing to fix the planner.  Maybe, the
> overhead of partitioning could be attributed to other parts of the system.

> Actually, I wrote that for patch 0002.  The next patch (0003) is meant to
> fix that.  So, the overhead you're seeing is even after making sure that
> only the selected partitions are locked.

Thanks for telling your thought.  I understood we should find the bottleneck with profiling first.


Regards
Takayuki Tsunakawa





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-08-30 09:29  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  6 siblings, 1 reply; 265+ messages in thread

From: Amit Langote @ 2018-08-30 09:29 UTC (permalink / raw)
  To: pgsql-hackers

On 2018/08/29 21:06, Amit Langote wrote:
> I measured the gain in performance due to each patch on a modest virtual
> machine.  Details of the measurement and results follow.
>
> UPDATE:
>
> nparts  master    0001   0002   0003
> ======  ======    ====   ====   ====
> 0         2856    2893   2862   2816
> 8          507    1115   1447   1872
> 16         260     765   1173   1892
> 32         119     483    922   1884
> 64          59     282    615   1881
> 128         29     153    378   1835
> 256         14      79    210   1803
> 512          5      40    113   1728
> 1024         2      17     57   1616
> 2048         0*      9     30   1471
> 4096         0+      4     15   1236
> 8192         0=      2      7    975
>
> For SELECT:
>
> nparts  master    0001   0002   0003
> ======  ======    ====   ====   ====
> 0         2290    2329   2319   2268
> 8         1058    1077   1414   1788
> 16         711     729   1124   1789
> 32         450     475    879   1773
> 64         265     272    603   1765
> 128        146     149    371   1685
> 256	      76      77    214   1678
> 512	      39      39    112   1636
> 1024	      16      17     59   1525
> 2048	       8       9     29   1416
> 4096	       4       4     15   1195
> 8192	       2       2      7    932

Prompted by Tsunakawa-san's comment, I tried to look at the profiles when
running the benchmark with partitioning and noticed a few things that made
clear why, even with 0003 applied, tps numbers decreased as the number of
partitions increased.  Some functions that appeared high up in the
profiles were related to partitioning:

* set_relation_partition_info calling partition_bounds_copy(), which calls
  datumCopy() on N Datums, where N is the number of partitions.  The more
  the number of partitions, higher up it is in profiles.  I suspect that
  this copying might be redundant; planner can keep using the same pointer
  as relcache

There are a few existing and newly introduced sites in the planner where
the code iterates over *all* partitions of a table where processing just
the partition selected for scanning would suffice.  I observed the
following functions in profiles:

* make_partitionedrel_pruneinfo, which goes over all partitions to
  generate subplan_map and subpart_map arrays to put into the
  PartitionedRelPruneInfo data structure that it's in the charge of
  generating

* apply_scanjoin_target_to_paths, which goes over all partitions to adjust
  their Paths for applying required scanjoin target, although most of
  those are dummy ones that won't need the adjustment

* For UPDATE, a couple of functions I introduced in patch 0001 were doing
  the same thing as apply_scanjoin_target_to_paths, which is unnecessary

To fix the above three instances of redundant processing, I added a
Bitmapset 'live_parts' to the RelOptInfo which stores the set of indexes
of only the unpruned partitions (into the RelOptInfo.part_rels array) and
replaced the for (i = 0; i < rel->nparts; i++) loops in those sites with
the loop that iterates over the members of 'live_parts'.

Results looked were promising indeed, especially after applying 0003 which
gets rid of locking all partitions.

UPDATE:

nparts  master    0001    0002   0003
======  ======    ====    ====   ====
0         2856    2893    2862   2816
8          507    1115    1466   1845
16         260     765    1161   1876
32         119     483     910   1862
64          59     282     609   1895
128         29     153     376   1884
256         14      79     212   1874
512          5      40     115   1859
1024         2      17      58   1847
2048         0       9      29   1883
4096         0       4      15   1867
8192         0       2       7   1826

SELECT:

nparts  master    0001    0002   0003
======  ======    ====    ====   ====
0       2290      2329    2319   2268
8       1058      1077    1431   1800
16       711       729    1158   1781
32       450       475     908   1777
64       265       272     612   1791
128      146       149     379   1777
256       76        77     213   1785
512       39        39     114   1776
1024      16        17      59   1756
2048       8         9      30   1746
4096       4         4      15   1722
8192       2         2       7   1706

Note that with 0003, tps doesn't degrade as the number of partitions increase.

Attached updated patches, with 0002 containing the changes mentioned above.

Thanks,
Amit

Attachments: download all (3 files, .tar.gz)

v2-0001-Overhaul-partitioned-table-update-delete-planning.patch (text/plain, 26.7K) download From 060bd2445ea9cba9adadd73505689d6f06583ee8 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 24 Aug 2018 12:39:36 +0900 Subject: [PATCH v2 1/3] Overhaul partitioned table update/delete planning Current method, inheritance_planner, applies grouping_planner and hence query_planner to the query repeatedly with each leaf partition replacing the root parent as the query's result relation. One big drawback of this approach is that it cannot use partprune.c to perform partition pruning on the partitioned result relation, because it can only be invoked if query_planner sees the partitioned relation itself in the query. That is not true with the existing method, because as mentioned above, query_planner is invoked with the partitioned relation replaced with individual leaf partitions. While most of the work in each repitition of grouping_planner (and query_planner) is same, a couple of things may differ from partition to partition -- 1. Join planning may produce different Paths for joining against different result partitions, 2. grouping_planner may produce different top-level target lists for different partitions, based on their TupleDescs. This commit rearranges things so that, only the planning steps that affect 1 and 2 above are repeated for partitions that are selected by query_planner by applying partprune.c based pruning to the original partitioned result rel. That makes things faster because 1. partprune.c based pruning is used instead of using constraint exclusion for each partition, 2. grouping_planner (and query_planner) is invoked only once instead of for every partition thus saving cycles and memory. This still doesn't help much if no partitions are pruned, because we still repeat join planning and makes copies of the query for each partition, but for common cases where only handful partitions remain after pruning, this makes things significanly faster. --- doc/src/sgml/ddl.sgml | 15 +- src/backend/optimizer/path/allpaths.c | 97 ++++++- src/backend/optimizer/plan/planmain.c | 4 +- src/backend/optimizer/plan/planner.c | 378 ++++++++++++++++++++------- src/backend/optimizer/prep/prepunion.c | 28 +- src/backend/optimizer/util/plancat.c | 30 --- src/test/regress/expected/partition_join.out | 4 +- 7 files changed, 416 insertions(+), 140 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index b5ed1b7939..53c479fbb8 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -3933,16 +3933,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <xref linkend="guc-enable-partition-pruning"/> setting. </para> - <note> - <para> - Currently, pruning of partitions during the planning of an - <command>UPDATE</command> or <command>DELETE</command> command is - implemented using the constraint exclusion method (however, it is - controlled by the <literal>enable_partition_pruning</literal> rather than - <literal>constraint_exclusion</literal>) &mdash; see the following section - for details and caveats that apply. - </para> - <para> Execution-time partition pruning currently occurs for the <literal>Append</literal> and <literal>MergeAppend</literal> node types. @@ -3964,9 +3954,8 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <para> <firstterm>Constraint exclusion</firstterm> is a query optimization - technique similar to partition pruning. While it is primarily used - for partitioning implemented using the legacy inheritance method, it can be - used for other purposes, including with declarative partitioning. + technique similar to partition pruning. It is primarily used + for partitioning implemented using the legacy inheritance method. </para> <para> diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 0e80aeb65c..5937c0436a 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -36,6 +36,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" @@ -119,6 +120,9 @@ static void set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); +static RelOptInfo *partitionwise_make_rel_from_joinlist(PlannerInfo *root, + RelOptInfo *parent, + List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, pushdown_safety_info *safetyInfo); static bool recurse_pushdown_safe(Node *setOp, Query *topquery, @@ -181,13 +185,30 @@ make_one_rel(PlannerInfo *root, List *joinlist) /* * Generate access paths for the entire join tree. + * + * If we're doing this for an UPDATE or DELETE query whose target is a + * partitioned table, we must do the join planning against each of its + * leaf partitions instead. */ - rel = make_rel_from_joinlist(root, joinlist); + if (root->parse->resultRelation && + root->parse->commandType != CMD_INSERT && + root->simple_rel_array[root->parse->resultRelation] && + root->simple_rel_array[root->parse->resultRelation]->part_scheme) + { + RelOptInfo *rootrel = root->simple_rel_array[root->parse->resultRelation]; - /* - * The result should join all and only the query's base rels. - */ - Assert(bms_equal(rel->relids, root->all_baserels)); + rel = partitionwise_make_rel_from_joinlist(root, rootrel, joinlist); + } + else + { + rel = make_rel_from_joinlist(root, joinlist); + + /* + * The result should join all and only the query's base rels. + */ + Assert(bms_equal(rel->relids, root->all_baserels)); + + } return rel; } @@ -2591,6 +2612,72 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows) } /* + * partitionwise_make_rel_from_joinlist + * performs join planning against each of the leaf partitions contained + * in the partition tree whose root relation is 'parent' + * + * Recursively called for each partitioned table contained in a given + *partition tree. + */ +static RelOptInfo * +partitionwise_make_rel_from_joinlist(PlannerInfo *root, + RelOptInfo *parent, + List *joinlist) +{ + int i; + + Assert(root->parse->resultRelation != 0); + Assert(parent->part_scheme != NULL); + + for (i = 0; i < parent->nparts; i++) + { + RelOptInfo *partrel = parent->part_rels[i]; + AppendRelInfo *appinfo; + List *translated_joinlist; + List *saved_join_info_list = list_copy(root->join_info_list); + + /* Ignore pruned partitions. */ + if (IS_DUMMY_REL(partrel)) + continue; + + /* + * Hack to make the join planning code believe that 'partrel' can + * be joined against. + */ + partrel->reloptkind = RELOPT_BASEREL; + + /* + * Replace references to the parent rel in expressions relevant to join + * planning. + */ + appinfo = root->append_rel_array[partrel->relid]; + translated_joinlist = (List *) + adjust_appendrel_attrs(root, (Node *) joinlist, + 1, &appinfo); + root->join_info_list = (List *) + adjust_appendrel_attrs(root, + (Node *) root->join_info_list, + 1, &appinfo); + /* Reset join planning data structures for a new partition. */ + root->join_rel_list = NIL; + root->join_rel_hash = NULL; + + /* Recurse if the partition is itself a partitioned table. */ + if (partrel->part_scheme != NULL) + partrel = partitionwise_make_rel_from_joinlist(root, partrel, + translated_joinlist); + else + /* Perform the join planning and save the resulting relation. */ + parent->part_rels[i] = + make_rel_from_joinlist(root, translated_joinlist); + + root->join_info_list = saved_join_info_list; + } + + return parent; +} + +/* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. * diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index b05adc70c4..3f0d80eaa6 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -266,7 +266,9 @@ query_planner(PlannerInfo *root, List *tlist, /* Check that we got at least one usable path */ if (!final_rel || !final_rel->cheapest_total_path || - final_rel->cheapest_total_path->param_info != NULL) + final_rel->cheapest_total_path->param_info != NULL || + (final_rel->relid == root->parse->resultRelation && + root->parse->commandType == CMD_INSERT)) elog(ERROR, "failed to construct the join relation"); return final_rel; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 96bf0601a8..076dbd3d62 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -238,6 +238,16 @@ static bool group_by_has_partkey(RelOptInfo *input_rel, List *targetList, List *groupClause); +static void partitionwise_adjust_scanjoin_target(PlannerInfo *root, + RelOptInfo *parent, + List **partition_subroots, + List **partitioned_rels, + List **resultRelations, + List **subpaths, + List **WCOLists, + List **returningLists, + List **rowMarks); + /***************************************************************************** * @@ -959,7 +969,9 @@ subquery_planner(PlannerGlobal *glob, Query *parse, * needs special processing, else go straight to grouping_planner. */ if (parse->resultRelation && - rt_fetch(parse->resultRelation, parse->rtable)->inh) + rt_fetch(parse->resultRelation, parse->rtable)->inh && + rt_fetch(parse->resultRelation, parse->rtable)->relkind != + RELKIND_PARTITIONED_TABLE) inheritance_planner(root); else grouping_planner(root, false, tuple_fraction); @@ -1688,6 +1700,14 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, RelOptInfo *current_rel; RelOptInfo *final_rel; ListCell *lc; + List *orig_parse_tlist = list_copy(parse->targetList); + List *partition_subroots = NIL; + List *partitioned_rels = NIL; + List *partition_resultRelations = NIL; + List *partition_subpaths = NIL; + List *partition_WCOLists = NIL; + List *partition_returningLists = NIL; + List *partition_rowMarks = NIL; /* Tweak caller-supplied tuple_fraction if have LIMIT/OFFSET */ if (parse->limitCount || parse->limitOffset) @@ -2018,13 +2038,44 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, scanjoin_targets_contain_srfs = NIL; } - /* Apply scan/join target. */ - scanjoin_target_same_exprs = list_length(scanjoin_targets) == 1 - && equal(scanjoin_target->exprs, current_rel->reltarget->exprs); - apply_scanjoin_target_to_paths(root, current_rel, scanjoin_targets, - scanjoin_targets_contain_srfs, - scanjoin_target_parallel_safe, - scanjoin_target_same_exprs); + /* + * For an UPDATE/DELETE query whose target is partitioned table, we + * must generate the targetlist for each of its leaf partitions and + * apply that. + */ + if (current_rel->reloptkind == RELOPT_BASEREL && + current_rel->part_scheme && + current_rel->relid == root->parse->resultRelation && + parse->commandType != CMD_INSERT) + { + /* + * scanjoin_target shouldn't have changed from final_target, + * because UPDATE/DELETE doesn't support various features that + * would've required modifications that are performed above. + * That's important because we'll generate final_target freshly + * for each partition in partitionwise_adjust_scanjoin_target. + */ + Assert(scanjoin_target == final_target); + root->parse->targetList = orig_parse_tlist; + partitionwise_adjust_scanjoin_target(root, current_rel, + &partition_subroots, + &partitioned_rels, + &partition_resultRelations, + &partition_subpaths, + &partition_WCOLists, + &partition_returningLists, + &partition_rowMarks); + } + else + { + /* Apply scan/join target. */ + scanjoin_target_same_exprs = list_length(scanjoin_targets) == 1 + && equal(scanjoin_target->exprs, current_rel->reltarget->exprs); + apply_scanjoin_target_to_paths(root, current_rel, scanjoin_targets, + scanjoin_targets_contain_srfs, + scanjoin_target_parallel_safe, + scanjoin_target_same_exprs); + } /* * Save the various upper-rel PathTargets we just computed into @@ -2136,93 +2187,119 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, final_rel->useridiscurrent = current_rel->useridiscurrent; final_rel->fdwroutine = current_rel->fdwroutine; - /* - * Generate paths for the final_rel. Insert all surviving paths, with - * LockRows, Limit, and/or ModifyTable steps added if needed. - */ - foreach(lc, current_rel->pathlist) + if (current_rel->reloptkind == RELOPT_BASEREL && + current_rel->relid == root->parse->resultRelation && + current_rel->part_scheme && + parse->commandType != CMD_INSERT) { - Path *path = (Path *) lfirst(lc); - - /* - * If there is a FOR [KEY] UPDATE/SHARE clause, add the LockRows node. - * (Note: we intentionally test parse->rowMarks not root->rowMarks - * here. If there are only non-locking rowmarks, they should be - * handled by the ModifyTable node instead. However, root->rowMarks - * is what goes into the LockRows node.) - */ - if (parse->rowMarks) - { - path = (Path *) create_lockrows_path(root, final_rel, path, - root->rowMarks, - SS_assign_special_param(root)); - } - - /* - * If there is a LIMIT/OFFSET clause, add the LIMIT node. - */ - if (limit_needed(parse)) - { - path = (Path *) create_limit_path(root, final_rel, path, - parse->limitOffset, - parse->limitCount, - offset_est, count_est); - } - - /* - * If this is an INSERT/UPDATE/DELETE, and we're not being called from - * inheritance_planner, add the ModifyTable node. - */ - if (parse->commandType != CMD_SELECT && !inheritance_update) - { - List *withCheckOptionLists; - List *returningLists; - List *rowMarks; - - /* - * Set up the WITH CHECK OPTION and RETURNING lists-of-lists, if - * needed. - */ - if (parse->withCheckOptions) - withCheckOptionLists = list_make1(parse->withCheckOptions); - else - withCheckOptionLists = NIL; - - if (parse->returningList) - returningLists = list_make1(parse->returningList); - else - returningLists = NIL; - - /* - * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node - * will have dealt with fetching non-locked marked rows, else we - * need to have ModifyTable do that. - */ - if (parse->rowMarks) - rowMarks = NIL; - else - rowMarks = root->rowMarks; - - path = (Path *) + Path *path = (Path *) create_modifytable_path(root, final_rel, parse->commandType, parse->canSetTag, parse->resultRelation, - NIL, - false, - list_make1_int(parse->resultRelation), - list_make1(path), - list_make1(root), - withCheckOptionLists, - returningLists, - rowMarks, - parse->onConflict, + partitioned_rels, + root->partColsUpdated, + partition_resultRelations, + partition_subpaths, + partition_subroots, + partition_WCOLists, + partition_returningLists, + partition_rowMarks, + NULL, SS_assign_special_param(root)); - } - - /* And shove it into final_rel */ add_path(final_rel, path); } + else + { + /* + * Generate paths for the final_rel. Insert all surviving paths, with + * LockRows, Limit, and/or ModifyTable steps added if needed. + */ + foreach(lc, current_rel->pathlist) + { + Path *path = (Path *) lfirst(lc); + + /* + * If there is a FOR [KEY] UPDATE/SHARE clause, add the LockRows + * node. (Note: we intentionally test parse->rowMarks not + * root->rowMarks here. If there are only non-locking rowmarks, + * they should be handled by the ModifyTable node instead. + * However, root->rowMarks is what goes into the LockRows node.) + */ + if (parse->rowMarks) + { + path = (Path *) + create_lockrows_path(root, final_rel, path, + root->rowMarks, + SS_assign_special_param(root)); + } + + /* + * If there is a LIMIT/OFFSET clause, add the LIMIT node. + */ + if (limit_needed(parse)) + { + path = (Path *) create_limit_path(root, final_rel, path, + parse->limitOffset, + parse->limitCount, + offset_est, count_est); + } + + /* + * If this is an INSERT/UPDATE/DELETE, and we're not being called + * from inheritance_planner, add the ModifyTable node. + */ + if (parse->commandType != CMD_SELECT && !inheritance_update) + { + List *withCheckOptionLists; + List *returningLists; + List *rowMarks; + + /* + * Set up the WITH CHECK OPTION and RETURNING lists-of-lists, + * if needed. + */ + if (parse->withCheckOptions) + withCheckOptionLists = list_make1(parse->withCheckOptions); + else + withCheckOptionLists = NIL; + + if (parse->returningList) + returningLists = list_make1(parse->returningList); + else + returningLists = NIL; + + /* + * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows + * node will have dealt with fetching non-locked marked rows, + * else we need to have ModifyTable do that. + */ + if (parse->rowMarks) + rowMarks = NIL; + else + rowMarks = root->rowMarks; + + path = (Path *) + create_modifytable_path(root, final_rel, + parse->commandType, + parse->canSetTag, + parse->resultRelation, + NIL, + false, + list_make1_int(parse->resultRelation), + list_make1(path), + list_make1(root), + withCheckOptionLists, + returningLists, + rowMarks, + parse->onConflict, + SS_assign_special_param(root)); + } + + /* And shove it into final_rel */ + add_path(final_rel, path); + } + } /* * Generate partial paths for final_rel, too, if outer query levels might @@ -2259,6 +2336,129 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, } /* + * partitionwise_adjust_scanjoin_target + * adjusts query's targetlist for each partition in the partition tree + * whose root is 'parent' and apply it to their paths via + * apply_scanjoin_target_to_paths + * + * Its output also consists of various pieces of information that will go + * into the ModifyTable node that will be created for this query. + */ +static void +partitionwise_adjust_scanjoin_target(PlannerInfo *root, + RelOptInfo *parent, + List **subroots, + List **partitioned_rels, + List **resultRelations, + List **subpaths, + List **WCOLists, + List **returningLists, + List **rowMarks) +{ + Query *parse = root->parse; + int i; + + *partitioned_rels = lappend(*partitioned_rels, + list_make1_int(parent->relid)); + + for (i = 0; i < parent->nparts; i++) + { + RelOptInfo *child_rel = parent->part_rels[i]; + AppendRelInfo *appinfo; + int relid; + List *tlist; + PathTarget *scanjoin_target; + bool scanjoin_target_parallel_safe; + bool scanjoin_target_same_exprs; + PlannerInfo *partition_subroot; + Query *partition_parse; + + /* Ignore pruned partitions. */ + if (IS_DUMMY_REL(child_rel)) + continue; + + /* + * Extract the original relid of partition to fetch its AppendRelInfo. + * We must find it like this, because + * partitionwise_make_rel_from_joinlist replaces the original rel + * with one generated by join planning which may be different. + */ + relid = -1; + while ((relid = bms_next_member(child_rel->relids, relid)) > 0) + if (root->append_rel_array[relid] && + root->append_rel_array[relid]->parent_relid == + parent->relid) + break; + + appinfo = root->append_rel_array[relid]; + + /* Translate Query structure for this partition. */ + partition_parse = (Query *) + adjust_appendrel_attrs(root, + (Node *) parse, + 1, &appinfo); + + /* Recurse if partition is itself a partitioned table. */ + if (child_rel->part_scheme) + { + root->parse = partition_parse; + partitionwise_adjust_scanjoin_target(root, child_rel, + subroots, + partitioned_rels, + resultRelations, + subpaths, + WCOLists, + returningLists, + rowMarks); + /* Restore the Query for processing the next partition. */ + root->parse = parse; + } + else + { + /* + * Generate a separate PlannerInfo for this partition. We'll need + * it when generating the ModifyTable subplan for this partition. + */ + partition_subroot = makeNode(PlannerInfo); + *subroots = lappend(*subroots, partition_subroot); + memcpy(partition_subroot, root, sizeof(PlannerInfo)); + partition_subroot->parse = partition_parse; + + /* + * Preprocess the translated targetlist and save it in the + * partition's PlannerInfo for the perusal of later planning + * steps. + */ + tlist = preprocess_targetlist(partition_subroot); + partition_subroot->processed_tlist = tlist; + + /* Apply scan/join target. */ + scanjoin_target = create_pathtarget(root, tlist); + scanjoin_target_same_exprs = equal(scanjoin_target->exprs, + child_rel->reltarget->exprs); + scanjoin_target_parallel_safe = + is_parallel_safe(root, (Node *) scanjoin_target->exprs); + apply_scanjoin_target_to_paths(root, child_rel, + list_make1(scanjoin_target), + NIL, + scanjoin_target_parallel_safe, + scanjoin_target_same_exprs); + + /* Collect information that will go into the ModifyTable */ + *resultRelations = lappend_int(*resultRelations, relid); + *subpaths = lappend(*subpaths, child_rel->cheapest_total_path); + if (partition_parse->withCheckOptions) + *WCOLists = lappend(*WCOLists, partition_parse->withCheckOptions); + if (partition_parse->returningList) + *returningLists = lappend(*returningLists, + partition_parse->returningList); + if (partition_parse->rowMarks) + *rowMarks = lappend(*rowMarks, partition_parse->rowMarks); + } + } +} + +/* * Do preprocessing for groupingSets clause and related data. This handles the * preliminary steps of expanding the grouping sets, organizing them into lists * of rollups, and preparing annotations which will later be filled in with @@ -6964,7 +7164,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, } /* Build new paths for this relation by appending child paths. */ - if (live_children != NIL) + if (live_children != NIL && + !(rel->reloptkind == RELOPT_BASEREL && + rel->relid == root->parse->resultRelation)) add_paths_to_append_rel(root, rel, live_children); } diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 690b6bbab7..f4c485cdc9 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -2265,8 +2265,34 @@ adjust_appendrel_attrs_mutator(Node *node, context->appinfos); return (Node *) phv; } + + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 8369e3ad62..8d67f21f42 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1265,36 +1265,6 @@ get_relation_constraints(PlannerInfo *root, } } - /* - * Append partition predicates, if any. - * - * For selects, partition pruning uses the parent table's partition bound - * descriptor, instead of constraint exclusion which is driven by the - * individual partition's partition constraint. - */ - if (enable_partition_pruning && root->parse->commandType != CMD_SELECT) - { - List *pcqual = RelationGetPartitionQual(relation); - - if (pcqual) - { - /* - * Run the partition quals through const-simplification similar to - * check constraints. We skip canonicalize_qual, though, because - * partition quals should be in canonical form already; also, - * since the qual is in implicit-AND format, we'd have to - * explicitly convert it to explicit-AND format and back again. - */ - pcqual = (List *) eval_const_expressions(root, (Node *) pcqual); - - /* Fix Vars to have the desired varno */ - if (varno != 1) - ChangeVarNodes((Node *) pcqual, 1, varno, 0); - - result = list_concat(result, pcqual); - } - } - heap_close(relation, NoLock); return result; diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index 7d04d12c6e..9074182512 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1752,7 +1752,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_1 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_1 -> Nested Loop Semi Join @@ -1760,7 +1760,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_2 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_2 (28 rows) -- 2.11.0
v2-0002-Lazy-creation-of-partition-objects-for-planning.patch (text/plain, 49.6K) download From 023ce5b76136c3a53bbf6401599d40362b2de719 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 16 May 2018 14:35:40 +0900 Subject: [PATCH v2 2/3] Lazy creation of partition objects for planning With the current approach, *all* partitions are opened and range table entries are created for them in the planner's prep phase, which is much sooner than when partition pruning is performed. This means that query_planner ends up spending cycles and memory on many partitions that potentially won't be included in the plan, such as creating RelOptInfos, AppendRelInfos. To avoid that, add partition range table entries and other planning data structures for only partitions that remain after applying partition pruning. Some code like that of partitionwise join rely on the fact that even though partitions may have been pruned, they would still have a RelOptInfo, albeit marked dummy to handle the outer join case where the pruned partition appears on the nullable side of join. So this commit also teaches the partitionwise join code to allocate dummy RelOptInfos for pruned partitions. There are couple of regression test diffs caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child and also that the individual partition RT entries are now created in the order in which their parent's are processed whereas previously they'd be added to the range table in the order of depth-first expansion of the tree. --- src/backend/optimizer/path/allpaths.c | 65 +++-- src/backend/optimizer/path/joinrels.c | 8 + src/backend/optimizer/plan/initsplan.c | 66 +++++ src/backend/optimizer/plan/planmain.c | 30 --- src/backend/optimizer/plan/planner.c | 19 +- src/backend/optimizer/prep/prepunion.c | 314 +++++++++------------- src/backend/optimizer/util/plancat.c | 16 +- src/backend/optimizer/util/relnode.c | 172 ++++++++++-- src/backend/partitioning/partprune.c | 109 ++++---- src/include/nodes/relation.h | 5 + src/include/optimizer/pathnode.h | 6 + src/include/optimizer/plancat.h | 2 +- src/include/optimizer/planmain.h | 3 + src/include/optimizer/prep.h | 10 + src/include/partitioning/partprune.h | 2 +- src/test/regress/expected/join.out | 22 +- src/test/regress/expected/partition_aggregate.out | 4 +- 17 files changed, 507 insertions(+), 346 deletions(-) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 5937c0436a..9abaab25fa 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -151,6 +151,7 @@ make_one_rel(PlannerInfo *root, List *joinlist) { RelOptInfo *rel; Index rti; + double total_pages; /* * Construct the all_baserels Relids set. @@ -181,6 +182,35 @@ make_one_rel(PlannerInfo *root, List *joinlist) * then generate access paths. */ set_base_rel_sizes(root); + + /* + * We should now have size estimates for every actual table involved in + * the query, and we also know which if any have been deleted from the + * query by join removal; so we can compute total_table_pages. + * + * Note that appendrels are not double-counted here, even though we don't + * bother to distinguish RelOptInfos for appendrel parents, because the + * parents will still have size zero. + * + * XXX if a table is self-joined, we will count it once per appearance, + * which perhaps is the wrong thing ... but that's not completely clear, + * and detecting self-joins here is difficult, so ignore it for now. + */ + total_pages = 0; + for (rti = 1; rti < root->simple_rel_array_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + + if (brel == NULL) + continue; + + Assert(brel->relid == rti); /* sanity check on array */ + + if (IS_SIMPLE_REL(brel)) + total_pages += (double) brel->pages; + } + root->total_table_pages = total_pages; + set_base_rel_pathlists(root); /* @@ -896,8 +926,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -913,21 +941,14 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, * partitioned table's list will contain all such indexes. */ if (rte->relkind == RELKIND_PARTITIONED_TABLE) + { rel->partitioned_child_rels = list_make1_int(rti); - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; + /* + * And do prunin. Note that this adds AppendRelInfo's of only the + * partitions that are not pruned. + */ + prune_append_rel_partitions(root, rel); } /* @@ -1178,13 +1199,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, continue; } - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - if (relation_excluded_by_constraints(root, childrel, childRTE)) { /* @@ -2629,16 +2643,15 @@ partitionwise_make_rel_from_joinlist(PlannerInfo *root, Assert(root->parse->resultRelation != 0); Assert(parent->part_scheme != NULL); - for (i = 0; i < parent->nparts; i++) + i = -1; + while ((i = bms_next_member(parent->live_parts, i)) >= 0) { RelOptInfo *partrel = parent->part_rels[i]; AppendRelInfo *appinfo; List *translated_joinlist; List *saved_join_info_list = list_copy(root->join_info_list); - /* Ignore pruned partitions. */ - if (IS_DUMMY_REL(partrel)) - continue; + Assert (partrel != NULL); /* * Hack to make the join planning code believe that 'partrel' can diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 7008e1318e..8542b95f4b 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1369,6 +1369,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts); + if (child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts); + /* We should never try to join two overlapping sets of rels. */ Assert(!bms_overlap(child_rel1->relids, child_rel2->relids)); child_joinrelids = bms_union(child_rel1->relids, child_rel2->relids); @@ -1407,6 +1412,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } } diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 01335db511..beb3e95101 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -132,6 +132,72 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode) (int) nodeTag(jtnode)); } +/* + * add_rel_partitions_to_query + * create range table entries and "otherrel" RelOptInfos and for the + * partitions of 'rel' specified by the caller + * + * To store the objects thus created, various arrays in 'root' are expanded + * by repalloc'ing them. + */ +void +add_rel_partitions_to_query(PlannerInfo *root, RelOptInfo *rel, + bool scan_all_parts, + Bitmapset *partindexes) +{ + int new_size; + int num_added_parts; + int i; + + Assert(partindexes != NULL || scan_all_parts); + + /* Expand the PlannerInfo arrays to hold new partition objects. */ + num_added_parts = scan_all_parts ? rel->nparts : + bms_num_members(partindexes); + new_size = root->simple_rel_array_size + num_added_parts; + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + if (root->append_rel_array) + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + else + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + + /* Set the contents of just allocated memory to 0. */ + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * num_added_parts); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * num_added_parts); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * num_added_parts); + root->simple_rel_array_size = new_size; + + /* And add the partitions. */ + if (scan_all_parts) + { + for (i = 0; i < rel->nparts; i++) + { + rel->part_rels[i] = build_partition_rel(root, rel, + rel->part_oids[i]); + rel->live_parts = bms_add_member(rel->live_parts, i); + } + } + else + { + rel->live_parts = partindexes; + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) + rel->part_rels[i] = build_partition_rel(root, rel, + rel->part_oids[i]); + } +} /***************************************************************************** * diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 3f0d80eaa6..1bd3f0e350 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -57,8 +57,6 @@ query_planner(PlannerInfo *root, List *tlist, Query *parse = root->parse; List *joinlist; RelOptInfo *final_rel; - Index rti; - double total_pages; /* * If the query has an empty join tree, then it's something easy like @@ -232,34 +230,6 @@ query_planner(PlannerInfo *root, List *tlist, extract_restriction_or_clauses(root); /* - * We should now have size estimates for every actual table involved in - * the query, and we also know which if any have been deleted from the - * query by join removal; so we can compute total_table_pages. - * - * Note that appendrels are not double-counted here, even though we don't - * bother to distinguish RelOptInfos for appendrel parents, because the - * parents will still have size zero. - * - * XXX if a table is self-joined, we will count it once per appearance, - * which perhaps is the wrong thing ... but that's not completely clear, - * and detecting self-joins here is difficult, so ignore it for now. - */ - total_pages = 0; - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - - if (brel == NULL) - continue; - - Assert(brel->relid == rti); /* sanity check on array */ - - if (IS_SIMPLE_REL(brel)) - total_pages += (double) brel->pages; - } - root->total_table_pages = total_pages; - - /* * Ready to do the primary planning. */ final_rel = make_one_rel(root, joinlist); diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 076dbd3d62..cce6757115 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -2361,7 +2361,8 @@ partitionwise_adjust_scanjoin_target(PlannerInfo *root, *partitioned_rels = lappend(*partitioned_rels, list_make1_int(parent->relid)); - for (i = 0; i < parent->nparts; i++) + i = -1; + while ((i = bms_next_member(parent->live_parts, i)) >= 0) { RelOptInfo *child_rel = parent->part_rels[i]; AppendRelInfo *appinfo; @@ -2373,9 +2374,7 @@ partitionwise_adjust_scanjoin_target(PlannerInfo *root, PlannerInfo *partition_subroot; Query *partition_parse; - /* Ignore pruned partitions. */ - if (IS_DUMMY_REL(child_rel)) - continue; + Assert (child_rel != NULL); /* * Extract the original relid of partition to fetch its AppendRelInfo. @@ -7122,18 +7121,21 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, */ if (rel->part_scheme && rel->part_rels) { - int partition_idx; + int i; List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + i = -1; + while ((i = bms_next_member(rel->live_parts, i)) >= 0) { - RelOptInfo *child_rel = rel->part_rels[partition_idx]; + RelOptInfo *child_rel = rel->part_rels[i]; ListCell *lc; AppendRelInfo **appinfos; int nappinfos; List *child_scanjoin_targets = NIL; + Assert(child_rel != NULL); + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -7237,6 +7239,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index f4c485cdc9..279f686fb0 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -49,6 +49,8 @@ #include "parser/parse_coerce.h" #include "parser/parsetree.h" #include "utils/lsyscache.h" +#include "utils/lsyscache.h" +#include "utils/partcache.h" #include "utils/rel.h" #include "utils/selfuncs.h" #include "utils/syscache.h" @@ -101,21 +103,10 @@ static List *generate_append_tlist(List *colTypes, List *colCollations, static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist); static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti); -static void expand_partitioned_rtentry(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); -static void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + RangeTblEntry *oldrte, RangeTblEntry *newrte, + Index newvarno, List **translated_vars); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, @@ -1522,6 +1513,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) LOCKMODE lockmode; List *inhOIDs; ListCell *l; + List *appinfos = NIL; /* Does RT entry allow inheritance? */ if (!rte->inh) @@ -1585,173 +1577,58 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) if (oldrc) oldrc->isParent = true; + /* Partitioned tables are expanded elsewhere. */ + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + { + list_free(inhOIDs); + return; + } + /* * Must open the parent relation to examine its tupdesc. We need not lock * it; we assume the rewriter already did. */ oldrelation = heap_open(parentOID, NoLock); - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) + foreach(l, inhOIDs) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); + Oid childOID = lfirst_oid(l); + Index childRTindex = 0; + RangeTblEntry *childrte = NULL; + AppendRelInfo *appinfo = NULL; - /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. - */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); + add_inheritance_child_to_query(root, rte, rti, + oldrelation->rd_rel->reltype, + RelationGetDescr(oldrelation), + oldrc, childOID, NoLock, + &appinfo, &childrte, + &childRTindex); + Assert(childRTindex > 1); + Assert(childrte != NULL); + Assert(appinfo != NULL); + appinfos = lappend(appinfos, appinfo); } + + /* + * If all the children were temp tables, pretend it's a + * non-inheritance situation; we don't need Append node in that case. + * The duplicate RTE we added for the parent table is harmless, so we + * don't bother to get rid of it; ditto for the useless PlanRowMark + * node. + */ + if (list_length(appinfos) < 2) + rte->inh = false; else - { - List *appinfos = NIL; - RangeTblEntry *childrte; - Index childRTindex; - - /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. - */ - foreach(l, inhOIDs) - { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = heap_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - heap_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - heap_close(newrelation, NoLock); - } - - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); - - } + root->append_rel_list = list_concat(root->append_rel_list, + appinfos); heap_close(oldrelation, NoLock); } /* - * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. - * - * Note that RelationGetPartitionDispatchInfo will expand partitions in the - * same order as this code. - */ -static void -expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) -{ - int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); - - check_stack_depth(); - - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); - - /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. - */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); - - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); - - /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. - */ - if (partdesc->nparts == 0) - { - parentrte->inh = false; - return; - } - - for (i = 0; i < partdesc->nparts; i++) - { - Oid childOID = partdesc->oids[i]; - Relation childrel; - - /* Open rel; we already have required locks */ - childrel = heap_open(childOID, NoLock); - - /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. - */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); - - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); - - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); - - /* Close child relation, but keep locks */ - heap_close(childrel, NoLock); - } -} - -/* - * expand_single_inheritance_child + * add_inheritance_child_to_query * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. + * maybe a PlanRowMark for a child relation. * * We now expand the partition hierarchy level by level, creating a * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each @@ -1769,19 +1646,70 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, * The child RangeTblEntry and its RTI are returned in "childrte_p" and * "childRTindex_p" resp. */ -static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +void +add_inheritance_child_to_query(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, Oid parentRelType, + TupleDesc parentDesc, + PlanRowMark *top_parentrc, + Oid childOID, int lockmode, + AppendRelInfo **appinfo_p, + RangeTblEntry **childrte_p, + Index *childRTindex_p) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); - Oid childOID = RelationGetRelid(childrel); + Oid parentOID = parentrte->relid; RangeTblEntry *childrte; Index childRTindex; AppendRelInfo *appinfo; + Relation childrel = NULL; + char child_relkind; + Oid child_reltype; + TupleDesc childDesc; + + *appinfo_p = NULL; + *childrte_p = NULL; + *childRTindex_p = 0; + + /* Open rel if needed; we already have required locks */ + if (childOID != parentOID) + { + childrel = heap_open(childOID, lockmode); + + /* + * Temporary partitions belonging to other sessions should have been + * disallowed at definition, but for paranoia's sake, let's double + * check. + */ + if (RELATION_IS_OTHER_TEMP(childrel)) + { + if (childrel->rd_rel->relispartition) + elog(ERROR, "temporary relation from another session found as partition"); + heap_close(childrel, lockmode); + return; + } + + child_relkind = childrel->rd_rel->relkind; + + /* + * No point in adding to the query a partitioned table that has no + * partitions. + */ + if (child_relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(childrel)->nparts == 0) + { + heap_close(childrel, lockmode); + return; + } + + child_reltype = childrel->rd_rel->reltype; + childDesc = RelationGetDescr(childrel); + } + else + { + child_relkind = parentrte->relkind; + child_reltype = parentRelType; + childDesc = parentDesc; + } /* * Build an RTE for the child, and attach to query's rangetable list. We @@ -1798,7 +1726,7 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, childrte = copyObject(parentrte); *childrte_p = childrte; childrte->relid = childOID; - childrte->relkind = childrel->rd_rel->relkind; + childrte->relkind = child_relkind; /* A partitioned child will need to be expanded further. */ if (childOID != parentOID && childrte->relkind == RELKIND_PARTITIONED_TABLE) @@ -1823,12 +1751,13 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, appinfo = makeNode(AppendRelInfo); appinfo->parent_relid = parentRTindex; appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->rd_rel->reltype; - appinfo->child_reltype = childrel->rd_rel->reltype; - make_inh_translation_list(parentrel, childrel, childRTindex, + appinfo->parent_reltype = parentRelType; + appinfo->child_reltype = child_reltype; + make_inh_translation_list(parentDesc, childDesc, + parentrte, childrte, childRTindex, &appinfo->translated_vars); appinfo->parent_reloid = parentOID; - *appinfos = lappend(*appinfos, appinfo); + *appinfo_p = appinfo; /* * Translate the column permissions bitmaps to the child's attnums (we @@ -1879,6 +1808,13 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } + + /* Close child relations, but keep locks */ + if (childOID != parentOID) + { + Assert(childrel != NULL); + heap_close(childrel, lockmode); + } } /* @@ -1889,14 +1825,12 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, * For paranoia's sake, we match type/collation as well as attribute name. */ static void -make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, - List **translated_vars) +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + RangeTblEntry *oldrte, RangeTblEntry *newrte, + Index newvarno, List **translated_vars) { List *vars = NIL; - TupleDesc old_tupdesc = RelationGetDescr(oldrelation); - TupleDesc new_tupdesc = RelationGetDescr(newrelation); - Oid new_relid = RelationGetRelid(newrelation); + Oid new_relid = newrte->relid; int oldnatts = old_tupdesc->natts; int newnatts = new_tupdesc->natts; int old_attno; @@ -1926,7 +1860,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, * When we are generating the "translation list" for the parent table * of an inheritance set, no need to search for matches. */ - if (oldrelation == newrelation) + if (oldrte->relid == newrte->relid) { vars = lappend(vars, makeVar(newvarno, (AttrNumber) (old_attno + 1), @@ -1955,7 +1889,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, newtup = SearchSysCacheAttName(new_relid, attname); if (!newtup) elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(newrte->relid)); new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; ReleaseSysCache(newtup); @@ -1965,10 +1899,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, /* Found it, check type and collation match */ if (atttypid != att->atttypid || atttypmod != att->atttypmod) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(newrte->relid)); if (attcollation != att->attcollation) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(newrte->relid)); vars = lappend(vars, makeVar(newvarno, (AttrNumber) (new_attno + 1), @@ -2121,7 +2055,7 @@ adjust_appendrel_attrs_mutator(Node *node, } } - if (var->varlevelsup == 0 && appinfo) + if (var->varlevelsup == 0 && appinfo && appinfo->translated_vars) { var->varno = appinfo->child_relid; var->varnoold = appinfo->child_relid; diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 8d67f21f42..f93cc6b90d 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -106,7 +106,7 @@ static void set_baserel_partition_key_exprs(Relation relation, */ void get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) + Bitmapset *updatedCols, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; @@ -449,7 +449,15 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * inheritance parents may be partitioned. */ if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { set_relation_partition_info(root, rel, relation); + if (!root->partColsUpdated) + root->partColsUpdated = + has_partition_attrs(relation, updatedCols, NULL); + } + + rel->tupdesc = RelationGetDescr(relation); + rel->reltype = RelationGetForm(relation)->reltype; heap_close(relation, NoLock); @@ -1871,18 +1879,18 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); + rel->boundinfo = partdesc->boundinfo; rel->nparts = partdesc->nparts; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); + rel->part_oids = (Oid *) palloc(rel->nparts * sizeof(Oid)); + memcpy(rel->part_oids, partdesc->oids, rel->nparts * sizeof(Oid)); } /* diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index c69740eda6..46ecb52166 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -16,6 +16,7 @@ #include <limits.h> +#include "catalog/pg_class.h" #include "miscadmin.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" @@ -27,6 +28,7 @@ #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" #include "partitioning/partbounds.h" +#include "storage/lockdefs.h" #include "utils/hsearch.h" @@ -137,6 +139,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) /* Rel should not exist already */ Assert(relid > 0 && relid < root->simple_rel_array_size); + if (root->simple_rel_array[relid] != NULL) elog(ERROR, "rel %d already exists", relid); @@ -218,7 +221,8 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte->relid, rte->inh, rte->updatedCols, + rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -268,41 +272,30 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) if (rte->inh) { ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - if (nparts > 0) + /* + * For partitioned tables, we just allocate space for RelOptInfo's. + * pointers for all partitions and copy the partition OIDs from the + * relcache. Actual RelOptInfo is built for a partition only if it is + * not pruned. + */ + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + { rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); + palloc0(sizeof(RelOptInfo *) * rel->nparts); + return rel; + } foreach(l, root->append_rel_list) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != relid) continue; - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; + (void) build_simple_rel(root, appinfo->child_relid, rel); } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); } return rel; @@ -1767,4 +1760,135 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; +} + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx) +{ + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (noop) AppendRelInfo for parent, because we're setting + * the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + AppendRelInfo *appinfo = makeNode(AppendRelInfo); + + appinfo->parent_relid = parent->relid; + appinfo->child_relid = parent->relid; + appinfo->parent_reltype = parent->reltype; + appinfo->child_reltype = parent->reltype; + /* leaving translated_vars to NIL to mean no translation needed */ + appinfo->parent_reloid = root->simple_rte_array[parent->relid]->relid; + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} + +/* + * build_partition_rel + * This adds a valid partition to the query by adding it to the + * range table and creating planner data structures for it + */ +RelOptInfo * +build_partition_rel(PlannerInfo *root, RelOptInfo *parent, Oid partoid) +{ + RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; + RelOptInfo *result; + Index partRTindex = 0; + RangeTblEntry *partrte = NULL; + AppendRelInfo *appinfo = NULL; + PlanRowMark *rootrc = NULL; + + /* Locate the root partitioned table and fetch its PlanRowMark, if any. */ + if (root->rowMarks) + { + Index rootRTindex = 0; + + /* + * The root partitioned table itself might be a child of UNION ALL + * parent, so we must resort to finding the root parent like this. + */ + rootRTindex = parent->relid; + if (root->append_rel_array[rootRTindex]) + { + AppendRelInfo *tmp = root->append_rel_array[rootRTindex]; + + /* + * Keep moving up until we each the parent rel that's not a + * partitioned table. The one before that one would be the root + * parent. + */ + while(root->simple_rel_array[rootRTindex]->part_scheme) + { + tmp = root->append_rel_array[tmp->parent_relid]; + if (tmp == NULL) + break; + rootRTindex = tmp->parent_relid; + } + } + + rootrc = get_plan_rowmark(root->rowMarks, rootRTindex); + } + + /* + * expand_inherited_rtentry alreay locked all partitions, so pass + * NoLock for lockmode. + */ + add_inheritance_child_to_query(root, parentrte, parent->relid, + parent->reltype, parent->tupdesc, + rootrc, partoid, NoLock, + &appinfo, &partrte, &partRTindex); + + /* Partition turned out to be a partitioned table with 0 partitions. */ + if (partrte == NULL) + return NULL; + + Assert(appinfo != NULL); + root->append_rel_list = lappend(root->append_rel_list, appinfo); + root->simple_rte_array[partRTindex] = partrte; + root->append_rel_array[partRTindex] = appinfo; + + /* Build the RelOptInfo. */ + result = build_simple_rel(root, partRTindex, parent); + + /* Set the information created by create_lateral_join_info(). */ + result->direct_lateral_relids = parent->direct_lateral_relids; + result->lateral_relids = parent->lateral_relids; + result->lateral_referencers = parent->lateral_referencers; + + return result; } diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index b5c1c7d4dd..b2b76f5af3 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -45,7 +45,9 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/pathnode.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/predtest.h" #include "optimizer/prep.h" @@ -437,26 +439,26 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); - present_parts = NULL; + memset(subpart_map, -1, nparts * sizeof(int)); + Assert(IS_SIMPLE_REL(subpart)); + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; + subplanidx = relid_subplan_map[partrel->relid] - 1; + subpartidx = relid_subpart_map[partrel->relid] - 1; subplan_map[i] = subplanidx; subpart_map[i] = subpartidx; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } rte = root->simple_rte_array[subpart->relid]; @@ -548,61 +550,68 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) * * Callers must ensure that 'rel' is a partitioned table. */ -Relids -prune_append_rel_partitions(RelOptInfo *rel) +void +prune_append_rel_partitions(PlannerInfo *root, RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; - bool contradictory; + bool contradictory, + scan_all_parts = false; PartitionPruneContext context; - Bitmapset *partindexes; - int i; + Bitmapset *partindexes = NULL; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ if (rel->nparts == 0) - return NULL; + return; - /* - * Process clauses. If the clauses are found to be contradictory, we can - * return the empty set. - */ - pruning_steps = gen_partprune_steps(rel, clauses, &contradictory); - if (contradictory) - return NULL; - - /* Set up PartitionPruneContext */ - context.strategy = rel->part_scheme->strategy; - context.partnatts = rel->part_scheme->partnatts; - context.nparts = rel->nparts; - context.boundinfo = rel->boundinfo; - context.partcollation = rel->part_scheme->partcollation; - context.partsupfunc = rel->part_scheme->partsupfunc; - context.stepcmpfuncs = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * + if (enable_partition_pruning && clauses != NIL) + { + /* + * Process clauses. If the clauses are found to be contradictory, we + * can return the empty set. + */ + pruning_steps = gen_partprune_steps(rel, clauses, &contradictory); + if (!contradictory) + { + context.strategy = rel->part_scheme->strategy; + context.partnatts = rel->part_scheme->partnatts; + context.nparts = rel->nparts; + context.boundinfo = rel->boundinfo; + context.partcollation = rel->part_scheme->partcollation; + context.partsupfunc = rel->part_scheme->partsupfunc; + context.stepcmpfuncs = (FmgrInfo *) + palloc0(sizeof(FmgrInfo) * context.partnatts * list_length(pruning_steps)); - context.ppccontext = CurrentMemoryContext; + context.ppccontext = CurrentMemoryContext; - /* These are not valid when being called from the planner */ - context.partrel = NULL; - context.planstate = NULL; - context.exprstates = NULL; - context.exprhasexecparam = NULL; - context.evalexecparams = false; + /* These are not valid when being called from the planner */ + context.partrel = NULL; + context.planstate = NULL; + context.exprstates = NULL; + context.exprhasexecparam = NULL; + context.evalexecparams = false; - /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); + /* Actual pruning happens here. */ + partindexes = get_matching_partitions(&context, pruning_steps); - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); + /* No need to add partitions if all were pruned. */ + if (bms_is_empty(partindexes)) + return; + } + else + scan_all_parts = true; + } + else + scan_all_parts = true; - return result; + /* + * Build selected partitions' range table entries, RelOptInfos, and + * AppendRelInfos. + */ + add_rel_partitions_to_query(root, rel, scan_all_parts, partindexes); } /* diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 41caf873fb..02c5bdc73f 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -15,6 +15,7 @@ #define RELATION_H #include "access/sdir.h" +#include "access/tupdesc.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/params.h" @@ -695,11 +696,15 @@ typedef struct RelOptInfo int nparts; /* number of partitions */ struct PartitionBoundInfoData *boundinfo; /* Partition bounds */ List *partition_qual; /* partition constraint */ + Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* unpruned parts; NULL if all are live */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ + TupleDesc tupdesc; + Oid reltype; } RelOptInfo; /* diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 7c5ff22650..4f567765a4 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -297,5 +297,11 @@ extern RelOptInfo *build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel, RelOptInfo *inner_rel, RelOptInfo *parent_joinrel, List *restrictlist, SpecialJoinInfo *sjinfo, JoinType jointype); +extern RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + int partidx); +extern RelOptInfo *build_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + Oid partoid); #endif /* PATHNODE_H */ diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index 7d53cbbb87..edaf2a3b4f 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -26,7 +26,7 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); + bool inhparent, Bitmapset *updatedCols, RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index c8ab0280d2..1916a33467 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -73,6 +73,9 @@ extern int from_collapse_limit; extern int join_collapse_limit; extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode); +extern void add_rel_partitions_to_query(PlannerInfo *root, RelOptInfo *rel, + bool scan_all_parts, + Bitmapset *partindexes); extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist); extern void add_vars_to_targetlist(PlannerInfo *root, List *vars, Relids where_needed, bool create_new_ph); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 38608770a2..ca66f75544 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -49,6 +49,16 @@ extern RelOptInfo *plan_set_operations(PlannerInfo *root); extern void expand_inherited_tables(PlannerInfo *root); +extern void add_inheritance_child_to_query(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, Oid parentRelType, + TupleDesc parentDesc, + PlanRowMark *top_parentrc, + Oid childOID, int lockmode, + AppendRelInfo **appinfo_p, + RangeTblEntry **childrte_p, + Index *childRTindex_p); + extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h index b95c346bab..55a324583b 100644 --- a/src/include/partitioning/partprune.h +++ b/src/include/partitioning/partprune.h @@ -79,7 +79,7 @@ extern PartitionPruneInfo *make_partition_pruneinfo(PlannerInfo *root, List *subpaths, List *partitioned_rels, List *prunequal); -extern Relids prune_append_rel_partitions(RelOptInfo *rel); +extern void prune_append_rel_partitions(PlannerInfo *root, RelOptInfo *rel); extern Bitmapset *get_matching_partitions(PartitionPruneContext *context, List *pruning_steps); diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index dc6262be43..5f931591a6 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -5533,29 +5533,29 @@ select t1.b, ss.phv from join_ut1 t1 left join lateral (select t2.a as t2a, t3.a t3a, least(t1.a, t2.a, t3.a) phv from join_pt1 t2 join join_ut1 t3 on t2.a = t3.b) ss on t1.a = ss.t2a order by t1.a; - QUERY PLAN ------------------------------------------------------------------- + QUERY PLAN +-------------------------------------------------------------------- Sort - Output: t1.b, (LEAST(t1.a, t2.a, t3.a)), t1.a + Output: t1.b, (LEAST(t1.a, t2_1.a, t3.a)), t1.a Sort Key: t1.a -> Nested Loop Left Join - Output: t1.b, (LEAST(t1.a, t2.a, t3.a)), t1.a + Output: t1.b, (LEAST(t1.a, t2_1.a, t3.a)), t1.a -> Seq Scan on public.join_ut1 t1 Output: t1.a, t1.b, t1.c -> Hash Join - Output: t2.a, LEAST(t1.a, t2.a, t3.a) - Hash Cond: (t3.b = t2.a) + Output: t2_1.a, LEAST(t1.a, t2_1.a, t3.a) + Hash Cond: (t3.b = t2_1.a) -> Seq Scan on public.join_ut1 t3 Output: t3.a, t3.b, t3.c -> Hash - Output: t2.a + Output: t2_1.a -> Append - -> Seq Scan on public.join_pt1p1p1 t2 - Output: t2.a - Filter: (t1.a = t2.a) - -> Seq Scan on public.join_pt1p2 t2_1 + -> Seq Scan on public.join_pt1p1p1 t2_1 Output: t2_1.a Filter: (t1.a = t2_1.a) + -> Seq Scan on public.join_pt1p2 t2 + Output: t2.a + Filter: (t1.a = t2.a) (21 rows) select t1.b, ss.phv from join_ut1 t1 left join lateral diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index d286050c9a..d1ce6ad423 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) -- 2.11.0
v2-0003-Only-lock-partitions-that-will-be-scanned-by-a-qu.patch (text/plain, 3.3K) download From c768ffe75d37df9d12775a3477cab1b0f1314eb1 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Thu, 23 Aug 2018 17:30:18 +0900 Subject: [PATCH v2 3/3] Only lock partitions that will be scanned by a query --- src/backend/optimizer/prep/prepunion.c | 8 +++----- src/backend/optimizer/util/relnode.c | 17 ++++++++++------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 279f686fb0..6a2adb5f4d 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -1555,14 +1555,15 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) lockmode = AccessShareLock; /* Scan for all members of inheritance set, acquire needed locks */ - inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); + if (rte->relkind != RELKIND_PARTITIONED_TABLE) + inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); /* * Check that there's at least one descendant, else treat as no-child * case. This could happen despite above has_subclass() check, if table * once had a child but no longer does. */ - if (list_length(inhOIDs) < 2) + if (rte->relkind != RELKIND_PARTITIONED_TABLE && list_length(inhOIDs) < 2) { /* Clear flag before returning */ rte->inh = false; @@ -1579,10 +1580,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) /* Partitioned tables are expanded elsewhere. */ if (rte->relkind == RELKIND_PARTITIONED_TABLE) - { - list_free(inhOIDs); return; - } /* * Must open the parent relation to examine its tupdesc. We need not lock diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 46ecb52166..0a0d7bcd26 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1828,16 +1828,16 @@ build_partition_rel(PlannerInfo *root, RelOptInfo *parent, Oid partoid) { RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; RelOptInfo *result; + Index rootRTindex = 0; Index partRTindex = 0; RangeTblEntry *partrte = NULL; AppendRelInfo *appinfo = NULL; PlanRowMark *rootrc = NULL; + int lockmode; /* Locate the root partitioned table and fetch its PlanRowMark, if any. */ if (root->rowMarks) { - Index rootRTindex = 0; - /* * The root partitioned table itself might be a child of UNION ALL * parent, so we must resort to finding the root parent like this. @@ -1864,13 +1864,16 @@ build_partition_rel(PlannerInfo *root, RelOptInfo *parent, Oid partoid) rootrc = get_plan_rowmark(root->rowMarks, rootRTindex); } - /* - * expand_inherited_rtentry alreay locked all partitions, so pass - * NoLock for lockmode. - */ + /* Determine the correct lockmode to use. */ + if (rootRTindex == root->parse->resultRelation) + lockmode = RowExclusiveLock; + else if (rootrc && RowMarkRequiresRowShareLock(rootrc->markType)) + lockmode = RowShareLock; + else + lockmode = AccessShareLock; add_inheritance_child_to_query(root, parentrte, parent->relid, parent->reltype, parent->tupdesc, - rootrc, partoid, NoLock, + rootrc, partoid, lockmode, &appinfo, &partrte, &partRTindex); /* Partition turned out to be a partitioned table with 0 partitions. */ -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2018-08-31 10:48  Kato, Sho <kato-sho@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  6 siblings, 1 reply; 265+ messages in thread

From: Kato, Sho @ 2018-08-31 10:48 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers

Hi, Amit

Great!
With the total number of records being 6400, I benchmarked while increasing the number of partitions from 100 to 6400.
Applying three all patches, 20% performance improved for 100 partitions.

I have the same number of records for each partition, do you do the same?

Also, in my case, performance was better when not prepare.
I think these patches do not improve execute case, so we need faster runtime pruning patch[1], right?

Details of measurement conditions and results are as follows.

- base source
  master(@777e6ddf17) + Speeding up Insert v8 patch[1]

- table definition(e.g. 100 partition)
  create table test.accounts(aid serial, abalance int) partition by range(aid);
  create table test.accounts_history(id serial, aid int, delta int, mtime timestamp without time zone) partition by range(aid);
  
  create table test.account_part_1 partition of test.accounts for values from (1) to (65);
  create table test.account_part_2 partition of test.accounts for values from (65) to (129);
  ...
  create table test.account_part_100 partition of test.accounts for values from (6337) to (6400);
  
  create table test.ah_part_1 partition of test.accounts_history for values from (1) to (65);
  create table test.ah_part_2 partition of test.accounts_history for values from (65) to (129);
  ...
  create table test.ah_part_100 partition of test.accounts_history for values from (6337) to (6400);

- benchmark script
  \set aid random(1, 6400)
  \set delta random(-5000, 5000)
  BEGIN;
  UPDATE test.accounts SET abalance = abalance + :delta WHERE aid = :aid;
  SELECT abalance FROM test.accounts WHERE aid = :aid;
  INSERT INTO test.accounts_history (aid, delta, mtime) VALUES (:aid, :delta, CURRENT_TIMESTAMP);
  END;

- command option
  pgbench -d testdb -f benchmark.pgbench -T 180 -r -n -M prepare
  pgbench -d testdb -f benchmark.pgbench -T 180 -r -n
  
-results
  base source no prepared
   part_num |   tps_ex   | update_latency | select_latency | insert_latency 
  ----------+------------+----------------+----------------+----------------
        100 | 662.414805 |          0.357 |          0.265 |          0.421
        200 | 494.478431 |          0.439 |          0.349 |          0.579
        400 | 307.982089 |          0.651 |          0.558 |          0.927
        800 | 191.360676 |          0.979 |          0.876 |          1.548
       1600 |  75.344947 |          2.253 |          2.003 |          4.301
       3200 |  30.643902 |          5.716 |          4.955 |         10.118
       6400 |  16.726056 |         12.512 |          8.582 |         18.054

  0001 no prepared
   part_num |   tps_ex   | update_latency | select_latency | insert_latency 
  ----------+------------+----------------+----------------+----------------
        100 | 429.816329 |          0.811 |           0.75 |          0.365
        200 | 275.211531 |          1.333 |          1.248 |          0.501
        400 | 160.499833 |          2.384 |          2.252 |          0.754
        800 |  79.387776 |          4.935 |          4.698 |          1.468
       1600 |  24.787377 |         16.593 |         15.954 |          4.302
       3200 |   9.846421 |          42.96 |         42.139 |          8.848
       6400 |   4.919772 |          87.43 |         83.109 |          16.56

  0001 prepared
   part_num |   tps_ex   | update_latency | select_latency | insert_latency 
  ----------+------------+----------------+----------------+----------------
        100 | 245.100776 |          2.728 |          0.374 |          0.476
        200 | 140.249283 |          5.116 |          0.603 |          0.686
        400 |  67.608559 |         11.383 |          1.055 |          1.179
        800 |  23.085806 |         35.781 |          2.585 |          2.677
       1600 |   6.211247 |        141.093 |          7.096 |          6.785
       3200 |   1.808214 |        508.045 |         15.741 |         13.243
       6400 |   0.495635 |       1919.362 |         37.691 |         28.177

  0001 + 0002 no prepared
   part_num |   tps_ex   | update_latency | select_latency | insert_latency 
  ----------+------------+----------------+----------------+----------------
        100 |  682.53091 |          0.388 |           0.35 |           0.35
        200 | 469.906601 |          0.543 |          0.496 |           0.51
        400 | 321.915349 |           0.78 |          0.721 |          0.752
        800 | 201.620975 |          1.246 |          1.156 |          1.236
       1600 |  94.438204 |          2.612 |          2.335 |          2.745
       3200 |  38.292922 |          6.657 |          5.579 |          6.808
       6400 |   21.48462 |         11.989 |         10.104 |         12.601

  0001 + 0002 prepared
   part_num |   tps_ex   | update_latency | select_latency | insert_latency 
  ----------+------------+----------------+----------------+----------------
        100 |  591.10863 |          0.433 |          0.342 |          0.422
        200 | 393.223638 |          0.625 |          0.522 |          0.614
        400 | 253.672736 |          0.946 |          0.828 |          0.928
        800 | 146.840262 |          1.615 |          1.448 |          1.604
       1600 |  52.805593 |          4.656 |          3.811 |          4.473
       3200 |  21.461606 |          11.48 |           9.56 |         10.661
       6400 |  11.888232 |         22.869 |         16.841 |         18.871
       
  0001 + 0002 + 0003 no prepared
   part_num |   tps_ex   | update_latency | select_latency | insert_latency 
  ----------+------------+----------------+----------------+----------------
        100 | 798.962029 |          0.304 |          0.267 |          0.339
        200 | 577.893396 |          0.384 |          0.346 |          0.487
        400 | 426.542177 |          0.472 |          0.435 |          0.717
        800 | 288.616213 |           0.63 |          0.591 |          1.162
       1600 | 154.247034 |          1.056 |          0.987 |          2.384
       3200 |  59.711446 |          2.416 |          2.233 |          6.514
       6400 |  37.109761 |          3.387 |          3.099 |         11.762
       
  0001 + 0002 + 0003 prepared
   part_num |   tps_ex   | update_latency | select_latency | insert_latency 
  ----------+------------+----------------+----------------+----------------
        100 | 662.414805 |          0.357 |          0.265 |          0.421
        200 | 494.478431 |          0.439 |          0.349 |          0.579
        400 | 307.982089 |          0.651 |          0.558 |          0.927
        800 | 191.360676 |          0.979 |          0.876 |          1.548
       1600 |  75.344947 |          2.253 |          2.003 |          4.301
       3200 |  30.643902 |          5.716 |          4.955 |         10.118
       6400 |  16.726056 |         12.512 |          8.582 |         18.054
       
       
  Although it may not be related to this, when measured with pg11 beta2, somehow the performance was better.

  11beta2 + v1-0001-Speed-up-INSERT-and-UPDATE-on-partitioned-tables.patch[3] prepared
   part_num |   tps_ex    | update_latency | select_latency | insert_latency 
  ----------+-------------+----------------+----------------+----------------
     100    | 756.07228   |          0.942 |          0.091 |          0.123
   
[1] https://www.postgresql.org/message-id/CAKJS1f_QN-nmf6jCQ4gRU_8ab0zrd0ms-U%3D_Dj0KUARJiuGpOA%40mail.g...
[2] https://www.postgresql.org/message-id/CAKJS1f9T_32Xpb-p8cWwo5ezSfVhXviUW8QTWncP8ksPHDRK8g%40mail.gma...
[3] https://www.postgresql.org/message-id/CAKJS1f_1RJyFquuCKRFHTdcXqoPX-PYqAd7nz%3DGVBwvGh4a6xA%40mail.g...

regards,
Sho Kato
-----Original Message-----
From: Amit Langote [mailto:Langote_Amit_f8@lab.ntt.co.jp] 
Sent: Wednesday, August 29, 2018 9:06 PM
To: Pg Hackers <pgsql-hackers@postgresql.org>
Subject: speeding up planning with partitions

It is more or less well known that the planner doesn't perform well with more than a few hundred partitions even when only a handful of partitions are ultimately included in the plan.  Situation has improved a bit in PG
11 where we replaced the older method of pruning partitions one-by-one using constraint exclusion with a much faster method that finds relevant partitions by using partitioning metadata.  However, we could only use it for SELECT queries, because UPDATE/DELETE are handled by a completely different code path, whose structure doesn't allow it to call the new pruning module's functionality.  Actually, not being able to use the new pruning is not the only problem for UPDATE/DELETE, more on which further below.

While situation improved with new pruning where it could, there are still overheads in the way planner handles partitions.  As things stand today, it will spend cycles and allocate memory for partitions even before pruning is performed, meaning most of that effort could be for partitions that were better left untouched.  Currently, planner will lock, heap_open
*all* partitions, create range table entries and AppendRelInfos  for them, and finally initialize RelOptInfos for them, even touching the disk file of each partition in the process, in an earlier phase of planning.  All of that processing is vain for partitions that are pruned, because they won't be included in the final plan.  This problem grows worse as the number of partitions grows beyond thousands, because range table grows too big.

That could be fixed by delaying all that per-partition activity to a point where pruning has already been performed, so that we know the partitions to open and create planning data structures for, such as somewhere downstream to query_planner.  But before we can do that we must do something about the fact that UPDATE/DELETE won't be able to cope with that because the code path that currently handles the planning of UPDATE/DELETE on partitioned tables (inheritance_planner called from
subquery_planner) relies on AppendRelInfos for all partitions having been initialized by an earlier planning phase.  Delaying it to query_planner would be too late, because inheritance_planner calls query_planner for each partition, not for the parent.  That is, if query_planner, which is downstream to inheritance_planner, was in the charge of determining which partitions to open, the latter wouldn't know which partitions to call the former for. :)

That would be fixed if there is no longer this ordering dependency, which is what I propose to do with the attached patch 0001.  I've tried to describe how the patch manages to do that in its commit message, but I'll summarize here.  As things stand today, inheritance_planner modifies the query for each leaf partition to make the partition act as the query's result relation instead of the original partitioned table and calls grouping_planner on the query.  That means anything that's joined to partitioned table looks to instead be joined to the partition and join paths are generated likewise.  Also, the resulting path's targetlist is adjusted to be suitable for the result partition.  Upon studying how this works, I concluded that the same result can be achieved if we call grouping_planner only once and repeat the portions of query_planner's and grouping_planner's processing that generate the join paths and appropriate target list, respectively, for each partition.  That way, we can rely on query_planner determining result partitions for us, which in turn relies on the faster partprune.c based method of pruning.  That speeds things up in two ways.  Faster pruning and we no longer repeat common processing for each partition.


With 0001 in place, there is nothing that requires that partitions be opened by an earlier planning phase, so, I propose patch 0002, which refactors the opening and creation of planner data structures for partitions such that it is now performed after pruning. However, it doesn't do anything about the fact that partitions are all still locked in the earlier phase.

With various overheads gone thanks to 0001 and 0002, locking of all partitions via find_all_inheritos can be seen as the single largest bottleneck, which 0003 tries to address.  I've kept it a separate patch, because I'll need to think a bit more to say that it's actually to safe to defer locking to late planning, due mainly to the concern about the change in the order of locking from the current method.  I'm attaching it here, because I also want to show the performance improvement we can expect with it.


I measured the gain in performance due to each patch on a modest virtual machine.  Details of the measurement and results follow.

* Benchmark scripts

update.sql
update ht set a = 0 where b = 1;

select.sql
select * from ht where b = 1;

* Table:

create table ht (a int, b int) partition by hash (b) create table ht_1 partition of ht for values with (modulus N, remainder 0) ..
create table ht_N partition of ht for values with (modulus N, remainder N-1)

* Rounded tps with update.sql and select.sql against regular table (nparts = 0) and partitioned table with various partition counts:

pgbench -n -T 60 -f update.sql

nparts  master    0001   0002   0003
======  ======    ====   ====   ====
0         2856    2893   2862   2816
8          507    1115   1447   1872
16         260     765   1173   1892
32         119     483    922   1884
64          59     282    615   1881
128         29     153    378   1835
256         14      79    210   1803
512          5      40    113   1728
1024         2      17     57   1616
2048         0*      9     30   1471
4096         0+      4     15   1236
8192         0=      2      7    975

* 0.46
+ 0.0064
= 0 (OOM on a virtual machine with 4GB RAM)

As can be seen here, 0001 is a big help for update queries.

pgbench -n -T 60 -f select.sql

For a select query that doesn't contain join and needs to scan only one
partition:

nparts  master    0001   0002   0003
======  ======    ====   ====   ====
0         2290    2329   2319   2268
8         1058    1077   1414   1788
16         711     729   1124   1789
32         450     475    879   1773
64         265     272    603   1765
128        146     149    371   1685
256	    76      77    214   1678
512	    39      39    112   1636
1024	    16      17     59   1525
2048	     8       9     29   1416
4096	     4       4     15   1195
8192	     2       2      7    932

Actually, here we get almost same numbers with 0001 as with master, because 0001 changes nothing for SELECT queries.  We start seeing improvement with 0002, the patch to delay opening partitions.

Thanks,
Amit



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-09-03 05:12  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Kato, Sho <kato-sho@jp.fujitsu.com>
  0 siblings, 1 reply; 265+ messages in thread

From: Amit Langote @ 2018-09-03 05:12 UTC (permalink / raw)
  To: Kato, Sho <kato-sho@jp.fujitsu.com>; pgsql-hackers

Thank you Kato-san for testing.

On 2018/08/31 19:48, Kato, Sho wrote:
> Hi, Amit
> 
> Great!
> With the total number of records being 6400, I benchmarked while increasing the number of partitions from 100 to 6400.
> Applying three all patches, 20% performance improved for 100 partitions.
> 
> I have the same number of records for each partition, do you do the same?

I didn't load any data into tables when running the tests, because these
patches are meant for reducing planner latency.  More specifically,
they're addressed to fix the current planner behavior that its latency
increases with increasing number of partitions, with focus on the common
case where only a single partition will need to be scanned by a given query.

I'd try to avoid using a benchmark whose results is affected by anything
other than the planning latency.  It will be a good idea if you leave the
tables empty and just vary the number of partitions and nothing else.

Also, we're interested in planning latency, so using just SELECT and
UPDATE in your benchmark script will be enough, because their planning
time is affected by the number of partitions.  No need to try to measure
the INSERT latency, because its planning latency is not affected by the
number of partitions.  Moreover, I'd rather suggest you take out the
INSERT statement from the benchmark for now, because its execution time
does vary unfavorably with increasing number of partitions.  Sure, there
are other patches to address that, but it's better not to mix the patches
to avoid confusion.

> Also, in my case, performance was better when not prepare.

Patches in this thread do nothing for the execution, so, there is no need
to compare prepared vs non-prepared.  In fact, just measure non-prepared
tps and latency, because we're only interested in planning time here.

> I think these patches do not improve execute case, so we need faster runtime pruning patch[1], right?

We already have run-time pruning code (that is the code in the patch you
linked) committed into the tree in PG 11, so the master tree also has it.
But since we're not interested in execution time, no need to worry about
run-time pruning.

> Details of measurement conditions and results are as follows.
> - base source
>   master(@777e6ddf17) + Speeding up Insert v8 patch[1]
>
> - table definition(e.g. 100 partition)
>   create table test.accounts(aid serial, abalance int)
>   partition by range(aid);
>   create table test.accounts_history(id serial, aid int, delta int,
>   mtime timestamp without time zone)
>   partition by range(aid);
>
> - command option
>   pgbench -d testdb -f benchmark.pgbench -T 180 -r -n -M prepare
>   pgbench -d testdb -f benchmark.pgbench -T 180 -r -n
>   
> -results
>   base source no prepared
>    part_num |   tps_ex   | update_latency | select_latency | insert_latency 
>   ----------+------------+----------------+----------------+----------------
>         100 | 662.414805 |          0.357 |          0.265 |          0.421
>         200 | 494.478431 |          0.439 |          0.349 |          0.579
>         400 | 307.982089 |          0.651 |          0.558 |          0.927
>         800 | 191.360676 |          0.979 |          0.876 |          1.548
>        1600 |  75.344947 |          2.253 |          2.003 |          4.301
>        3200 |  30.643902 |          5.716 |          4.955 |         10.118
>        6400 |  16.726056 |         12.512 |          8.582 |         18.054
> 
>   0001 no prepared
>    part_num |   tps_ex   | update_latency | select_latency | insert_latency 
>   ----------+------------+----------------+----------------+----------------
>         100 | 429.816329 |          0.811 |           0.75 |          0.365
>         200 | 275.211531 |          1.333 |          1.248 |          0.501
>         400 | 160.499833 |          2.384 |          2.252 |          0.754
>         800 |  79.387776 |          4.935 |          4.698 |          1.468
>        1600 |  24.787377 |         16.593 |         15.954 |          4.302
>        3200 |   9.846421 |          42.96 |         42.139 |          8.848
>        6400 |   4.919772 |          87.43 |         83.109 |          16.56

Hmm, since 0001 is meant to improve update planning time, it seems odd
that you'd get poorer results compared to base source.  But, it seems base
source is actually master + the patch to improve the execution time of
update, so maybe that patch is playing a part here, although I'm not sure
why even that's making this much of a difference.

I suggest that you use un-patched master as base source, that is, leave
out any patches to improve execution time.

[ ... ]

>   0001 + 0002 no prepared
>    part_num |   tps_ex   | update_latency | select_latency | insert_latency 
>   ----------+------------+----------------+----------------+----------------
>         100 |  682.53091 |          0.388 |           0.35 |           0.35
>         200 | 469.906601 |          0.543 |          0.496 |           0.51
>         400 | 321.915349 |           0.78 |          0.721 |          0.752
>         800 | 201.620975 |          1.246 |          1.156 |          1.236
>        1600 |  94.438204 |          2.612 |          2.335 |          2.745
>        3200 |  38.292922 |          6.657 |          5.579 |          6.808
>        6400 |   21.48462 |         11.989 |         10.104 |         12.601
> 

[ ... ]

>        
>   0001 + 0002 + 0003 no prepared
>    part_num |   tps_ex   | update_latency | select_latency | insert_latency 
>   ----------+------------+----------------+----------------+----------------
>         100 | 798.962029 |          0.304 |          0.267 |          0.339
>         200 | 577.893396 |          0.384 |          0.346 |          0.487
>         400 | 426.542177 |          0.472 |          0.435 |          0.717
>         800 | 288.616213 |           0.63 |          0.591 |          1.162
>        1600 | 154.247034 |          1.056 |          0.987 |          2.384
>        3200 |  59.711446 |          2.416 |          2.233 |          6.514
>        6400 |  37.109761 |          3.387 |          3.099 |         11.762
>        

[ ... ]

By the way, as you may have noticed, I posted a version 2 of the patches
on this thread.  If you apply them, you will be be able to see almost same
TPS for any number of partitions with master + 0001 + 0002 + 0003.

>   Although it may not be related to this, when measured with pg11 beta2, somehow the performance was better.
> 
>   11beta2 + v1-0001-Speed-up-INSERT-and-UPDATE-on-partitioned-tables.patch[3] prepared
>    part_num |   tps_ex    | update_latency | select_latency | insert_latency 
>   ----------+-------------+----------------+----------------+----------------
>      100    | 756.07228   |          0.942 |          0.091 |          0.123

I guess if you had applied the latest version of
"Speed-up-INSERT-and-UPDATE-on-partitioned-tables.patch" (which is v8
posted at [1]) on top of master + 0001 + 0002 + 0003, you'd get better
performance too.  But, as mentioned above, we're interested in measuring
planning latency, not execution latency, so we should leave out any
patches that are meant toward improving execution latency to avoid confusion.


Thanks again.

Regards,
Amit

[1]
https://www.postgresql.org/message-id/CAKJS1f9T_32Xpb-p8cWwo5ezSfVhXviUW8QTWncP8ksPHDRK8g%40mail.gma...





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-09-03 11:57  Dilip Kumar <dilipbalaut@gmail.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  6 siblings, 1 reply; 265+ messages in thread

From: Dilip Kumar @ 2018-09-03 11:57 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers

On Wed, Aug 29, 2018 at 5:36 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> It is more or less well known that the planner doesn't perform well with
> more than a few hundred partitions even when only a handful of partitions
> are ultimately included in the plan.  Situation has improved a bit in PG
> 11 where we replaced the older method of pruning partitions one-by-one
> using constraint exclusion with a much faster method that finds relevant
> partitions by using partitioning metadata.  However, we could only use it
> for SELECT queries, because UPDATE/DELETE are handled by a completely
> different code path, whose structure doesn't allow it to call the new
> pruning module's functionality.  Actually, not being able to use the new
> pruning is not the only problem for UPDATE/DELETE, more on which further
> below.
>
>
> pgbench -n -T 60 -f update.sql
>
> nparts  master    0001   0002   0003
> ======  ======    ====   ====   ====
> 0         2856    2893   2862   2816
> 8          507    1115   1447   1872
> 16         260     765   1173   1892
> 32         119     483    922   1884
> 64          59     282    615   1881
> 128         29     153    378   1835
> 256         14      79    210   1803
> 512          5      40    113   1728
> 1024         2      17     57   1616
> 2048         0*      9     30   1471
> 4096         0+      4     15   1236
> 8192         0=      2      7    975
>
> * 0.46
> + 0.0064
> = 0 (OOM on a virtual machine with 4GB RAM)
>

The idea looks interesting while going through the patch I observed
this comment.

/*
 * inheritance_planner
 *   Generate Paths in the case where the result relation is an
 *   inheritance set.
 *
 * We have to handle this case differently from cases where a source relation
 * is an inheritance set. Source inheritance is expanded at the bottom of the
 * plan tree (see allpaths.c), but target inheritance has to be expanded at
 * the top.

I think with your patch these comments needs to be change?


  if (parse->resultRelation &&
- rt_fetch(parse->resultRelation, parse->rtable)->inh)
+ rt_fetch(parse->resultRelation, parse->rtable)->inh &&
+ rt_fetch(parse->resultRelation, parse->rtable)->relkind !=
+ RELKIND_PARTITIONED_TABLE)
  inheritance_planner(root);
  else
  grouping_planner(root, false, tuple_fraction);

I think we can add some comments to explain if the target rel itself
is partitioned rel then why
we can directly go to the grouping planner.


-- 
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2018-09-04 05:45  Kato, Sho <kato-sho@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 0 replies; 265+ messages in thread

From: Kato, Sho @ 2018-09-04 05:45 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers

Hi, Amit. Thank you for your reply.

> I didn't load any data into tables when running the tests, because these patches are meant for reducing planner latency.  More specifically, they're addressed to fix the current planner behavior that its latency increases with increasing number of partitions, with focus on the common case where only a single partition will need to be scanned by a given query.

Thank you for telling me details. It is very helpful.

> No need to try to measure the INSERT latency, because its planning latency is not affected by the number of partitions.  Moreover, I'd rather suggest you take out the INSERT statement from the benchmark for now, because its execution time does vary unfavorably with increasing number of partitions.

Thank you for your advice.

> In fact, just measure non-prepared tps and latency, because we're only interested in planning time here.

I got it.

> Hmm, since 0001 is meant to improve update planning time, it seems odd that you'd get poorer results compared to base source.  But, it seems base source is actually master + the patch to improve the execution time of update, so maybe that patch is playing a part here, although I'm not sure why even that's making this much of a difference.
> I suggest that you use un-patched master as base source, that is, leave out any patches to improve execution time.

> By the way, as you may have noticed, I posted a version 2 of the patches on this thread.  If you apply them, you will be be able to see almost same TPS for any number of partitions with master + 0001 + 0002 + 0003.

> I guess if you had applied the latest version of "Speed-up-INSERT-and-UPDATE-on-partitioned-tables.patch" (which is v8 posted at [1]) on top of master + 0001 + 0002 + 0003, you'd get better performance too.

Thank you. I will try out these cases.

Thanks,

--
Sho Kato
-----Original Message-----
From: Amit Langote [mailto:Langote_Amit_f8@lab.ntt.co.jp] 
Sent: Monday, September 3, 2018 2:12 PM
To: Kato, Sho/加藤 翔 <kato-sho@jp.fujitsu.com>; Pg Hackers <pgsql-hackers@postgresql.org>
Subject: Re: speeding up planning with partitions

Thank you Kato-san for testing.

On 2018/08/31 19:48, Kato, Sho wrote:
> Hi, Amit
> 
> Great!
> With the total number of records being 6400, I benchmarked while increasing the number of partitions from 100 to 6400.
> Applying three all patches, 20% performance improved for 100 partitions.
> 
> I have the same number of records for each partition, do you do the same?

I didn't load any data into tables when running the tests, because these patches are meant for reducing planner latency.  More specifically, they're addressed to fix the current planner behavior that its latency increases with increasing number of partitions, with focus on the common case where only a single partition will need to be scanned by a given query.

I'd try to avoid using a benchmark whose results is affected by anything other than the planning latency.  It will be a good idea if you leave the tables empty and just vary the number of partitions and nothing else.

Also, we're interested in planning latency, so using just SELECT and UPDATE in your benchmark script will be enough, because their planning time is affected by the number of partitions.  No need to try to measure the INSERT latency, because its planning latency is not affected by the number of partitions.  Moreover, I'd rather suggest you take out the INSERT statement from the benchmark for now, because its execution time does vary unfavorably with increasing number of partitions.  Sure, there are other patches to address that, but it's better not to mix the patches to avoid confusion.

> Also, in my case, performance was better when not prepare.

Patches in this thread do nothing for the execution, so, there is no need to compare prepared vs non-prepared.  In fact, just measure non-prepared tps and latency, because we're only interested in planning time here.

> I think these patches do not improve execute case, so we need faster runtime pruning patch[1], right?

We already have run-time pruning code (that is the code in the patch you
linked) committed into the tree in PG 11, so the master tree also has it.
But since we're not interested in execution time, no need to worry about run-time pruning.

> Details of measurement conditions and results are as follows.
> - base source
>   master(@777e6ddf17) + Speeding up Insert v8 patch[1]
>
> - table definition(e.g. 100 partition)
>   create table test.accounts(aid serial, abalance int)
>   partition by range(aid);
>   create table test.accounts_history(id serial, aid int, delta int,
>   mtime timestamp without time zone)
>   partition by range(aid);
>
> - command option
>   pgbench -d testdb -f benchmark.pgbench -T 180 -r -n -M prepare
>   pgbench -d testdb -f benchmark.pgbench -T 180 -r -n
>   
> -results
>   base source no prepared
>    part_num |   tps_ex   | update_latency | select_latency | insert_latency 
>   ----------+------------+----------------+----------------+----------------
>         100 | 662.414805 |          0.357 |          0.265 |          0.421
>         200 | 494.478431 |          0.439 |          0.349 |          0.579
>         400 | 307.982089 |          0.651 |          0.558 |          0.927
>         800 | 191.360676 |          0.979 |          0.876 |          1.548
>        1600 |  75.344947 |          2.253 |          2.003 |          4.301
>        3200 |  30.643902 |          5.716 |          4.955 |         10.118
>        6400 |  16.726056 |         12.512 |          8.582 |         18.054
> 
>   0001 no prepared
>    part_num |   tps_ex   | update_latency | select_latency | insert_latency 
>   ----------+------------+----------------+----------------+----------------
>         100 | 429.816329 |          0.811 |           0.75 |          0.365
>         200 | 275.211531 |          1.333 |          1.248 |          0.501
>         400 | 160.499833 |          2.384 |          2.252 |          0.754
>         800 |  79.387776 |          4.935 |          4.698 |          1.468
>        1600 |  24.787377 |         16.593 |         15.954 |          4.302
>        3200 |   9.846421 |          42.96 |         42.139 |          8.848
>        6400 |   4.919772 |          87.43 |         83.109 |          16.56

Hmm, since 0001 is meant to improve update planning time, it seems odd that you'd get poorer results compared to base source.  But, it seems base source is actually master + the patch to improve the execution time of update, so maybe that patch is playing a part here, although I'm not sure why even that's making this much of a difference.

I suggest that you use un-patched master as base source, that is, leave out any patches to improve execution time.

[ ... ]

>   0001 + 0002 no prepared
>    part_num |   tps_ex   | update_latency | select_latency | insert_latency 
>   ----------+------------+----------------+----------------+----------------
>         100 |  682.53091 |          0.388 |           0.35 |           0.35
>         200 | 469.906601 |          0.543 |          0.496 |           0.51
>         400 | 321.915349 |           0.78 |          0.721 |          0.752
>         800 | 201.620975 |          1.246 |          1.156 |          1.236
>        1600 |  94.438204 |          2.612 |          2.335 |          2.745
>        3200 |  38.292922 |          6.657 |          5.579 |          6.808
>        6400 |   21.48462 |         11.989 |         10.104 |         12.601
> 

[ ... ]

>        
>   0001 + 0002 + 0003 no prepared
>    part_num |   tps_ex   | update_latency | select_latency | insert_latency 
>   ----------+------------+----------------+----------------+----------------
>         100 | 798.962029 |          0.304 |          0.267 |          0.339
>         200 | 577.893396 |          0.384 |          0.346 |          0.487
>         400 | 426.542177 |          0.472 |          0.435 |          0.717
>         800 | 288.616213 |           0.63 |          0.591 |          1.162
>        1600 | 154.247034 |          1.056 |          0.987 |          2.384
>        3200 |  59.711446 |          2.416 |          2.233 |          6.514
>        6400 |  37.109761 |          3.387 |          3.099 |         11.762
>        

[ ... ]

By the way, as you may have noticed, I posted a version 2 of the patches on this thread.  If you apply them, you will be be able to see almost same TPS for any number of partitions with master + 0001 + 0002 + 0003.

>   Although it may not be related to this, when measured with pg11 beta2, somehow the performance was better.
> 
>   11beta2 + v1-0001-Speed-up-INSERT-and-UPDATE-on-partitioned-tables.patch[3] prepared
>    part_num |   tps_ex    | update_latency | select_latency | insert_latency 
>   ----------+-------------+----------------+----------------+----------------
>      100    | 756.07228   |          0.942 |          0.091 |          0.123

I guess if you had applied the latest version of "Speed-up-INSERT-and-UPDATE-on-partitioned-tables.patch" (which is v8 posted at [1]) on top of master + 0001 + 0002 + 0003, you'd get better performance too.  But, as mentioned above, we're interested in measuring planning latency, not execution latency, so we should leave out any patches that are meant toward improving execution latency to avoid confusion.


Thanks again.

Regards,
Amit

[1]
https://www.postgresql.org/message-id/CAKJS1f9T_32Xpb-p8cWwo5ezSfVhXviUW8QTWncP8ksPHDRK8g%40mail.gma...




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-09-04 07:14  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Dilip Kumar <dilipbalaut@gmail.com>
  0 siblings, 1 reply; 265+ messages in thread

From: Amit Langote @ 2018-09-04 07:14 UTC (permalink / raw)
  To: Dilip Kumar <dilipbalaut@gmail.com>; +Cc: pgsql-hackers

Hi Dilip,

Thanks for taking a look.

On 2018/09/03 20:57, Dilip Kumar wrote:
> The idea looks interesting while going through the patch I observed
> this comment.
> 
> /*
>  * inheritance_planner
>  *   Generate Paths in the case where the result relation is an
>  *   inheritance set.
>  *
>  * We have to handle this case differently from cases where a source relation
>  * is an inheritance set. Source inheritance is expanded at the bottom of the
>  * plan tree (see allpaths.c), but target inheritance has to be expanded at
>  * the top.
> 
> I think with your patch these comments needs to be change?

Yes, maybe a good idea to mention that partitioned table result relations
are not handled here.

Actually, I've been wondering if this patch (0001) shouldn't get rid of
inheritance_planner altogether and implement the new approach for *all*
inheritance sets, not just partitioned tables, but haven't spent much time
on that idea yet.

>   if (parse->resultRelation &&
> - rt_fetch(parse->resultRelation, parse->rtable)->inh)
> + rt_fetch(parse->resultRelation, parse->rtable)->inh &&
> + rt_fetch(parse->resultRelation, parse->rtable)->relkind !=
> + RELKIND_PARTITIONED_TABLE)
>   inheritance_planner(root);
>   else
>   grouping_planner(root, false, tuple_fraction);
> 
> I think we can add some comments to explain if the target rel itself
> is partitioned rel then why
> we can directly go to the grouping planner.

Okay, I will try to add more explanatory comments here and there in the
next version I will post later this week.

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-09-04 13:24  David Rowley <david.rowley@2ndquadrant.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  6 siblings, 1 reply; 265+ messages in thread

From: David Rowley @ 2018-09-04 13:24 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers

On 30 August 2018 at 00:06, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
> With various overheads gone thanks to 0001 and 0002, locking of all
> partitions via find_all_inheritos can be seen as the single largest
> bottleneck, which 0003 tries to address.  I've kept it a separate patch,
> because I'll need to think a bit more to say that it's actually to safe to
> defer locking to late planning, due mainly to the concern about the change
> in the order of locking from the current method.  I'm attaching it here,
> because I also want to show the performance improvement we can expect with it.

For now, find_all_inheritors() locks the tables in ascending Oid
order.  This makes sense with inheritance parent tables as it's much
cheaper to sort on this rather than on something like the table's
namespace and name.  I see no reason why what we sort on really
matters, as long as we always sort on the same thing and the key we
sort on is always unique so that the locking order is well defined.

For partitioned tables, there's really not much sense in sticking to
the same lock by Oid order. The order of the PartitionDesc is already
well defined so I don't see any reason why we can't just perform the
locking in PartitionDesc order. This would mean you could perform the
locking of the partitions once pruning is complete somewhere around
add_rel_partitions_to_query(). Also, doing this would remove the need
for scanning pg_inherits during find_all_inheritors() and would likely
further speed up the planning of queries to partitioned tables with
many partitions. I wrote a function named
get_partition_descendants_worker() to do this in patch 0002 in [1] (it
may have been a bad choice to have made this a separate function
rather than just part of find_all_inheritors() as it meant I had to
change a bit too much code in tablecmds.c). There might be something
you can salvage from that patch to help here.

[1] https://www.postgresql.org/message-id/CAKJS1f9QjUwQrio20Pi%3DyCHmnouf4z3SfN8sqXaAcwREG6k0zQ%40mail.g...

-- 
 David Rowley                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-09-05 05:25  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: David Rowley <david.rowley@2ndquadrant.com>
  0 siblings, 0 replies; 265+ messages in thread

From: Amit Langote @ 2018-09-05 05:25 UTC (permalink / raw)
  To: David Rowley <david.rowley@2ndquadrant.com>; +Cc: pgsql-hackers

On 2018/09/04 22:24, David Rowley wrote:
> On 30 August 2018 at 00:06, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> With various overheads gone thanks to 0001 and 0002, locking of all
>> partitions via find_all_inheritos can be seen as the single largest
>> bottleneck, which 0003 tries to address.  I've kept it a separate patch,
>> because I'll need to think a bit more to say that it's actually to safe to
>> defer locking to late planning, due mainly to the concern about the change
>> in the order of locking from the current method.  I'm attaching it here,
>> because I also want to show the performance improvement we can expect with it.
> 
> For now, find_all_inheritors() locks the tables in ascending Oid
> order.  This makes sense with inheritance parent tables as it's much
> cheaper to sort on this rather than on something like the table's
> namespace and name.  I see no reason why what we sort on really
> matters, as long as we always sort on the same thing and the key we
> sort on is always unique so that the locking order is well defined.
> 
> For partitioned tables, there's really not much sense in sticking to
> the same lock by Oid order. The order of the PartitionDesc is already
> well defined so I don't see any reason why we can't just perform the
> locking in PartitionDesc order.

The reason we do the locking with find_all_inheritors for regular queries
(planner (expand_inherited_rtentry) does it for select/update/delete and
executor (ExecSetupPartitionTupleRouting) for insert) is that that's the
order used by various DDL commands when locking partitions, such as when
adding a column.  So, we'd have one piece of code locking partitions by
Oid order and others by canonical partition bound order or PartitionDesc
order.  I'm no longer sure if that's problematic though, about which more
below.

> This would mean you could perform the
> locking of the partitions once pruning is complete somewhere around
> add_rel_partitions_to_query(). Also, doing this would remove the need
> for scanning pg_inherits during find_all_inheritors() and would likely
> further speed up the planning of queries to partitioned tables with
> many partitions.

That's what happens with 0003; note the following hunk in it:

@@ -1555,14 +1555,15 @@ expand_inherited_rtentry(PlannerInfo *root,
RangeTblEntry *rte, Index rti)
         lockmode = AccessShareLock;

     /* Scan for all members of inheritance set, acquire needed locks */
-    inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+    if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+        inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);

> I wrote a function named
> get_partition_descendants_worker() to do this in patch 0002 in [1] (it
> may have been a bad choice to have made this a separate function
> rather than just part of find_all_inheritors() as it meant I had to
> change a bit too much code in tablecmds.c). There might be something
> you can salvage from that patch to help here.
> 
> [1] https://www.postgresql.org/message-id/CAKJS1f9QjUwQrio20Pi%3DyCHmnouf4z3SfN8sqXaAcwREG6k0zQ%40mail.g...

Actually, I had written a similar patch to replace the usages of
find_all_inheritors and find_inheritance_children by different
partitioning-specific functions which would collect the the partition OIDs
from the already open parent table's PartitionDesc, more or less like the
patch you mention does.  But I think we don't need any new function(s) to
do that, that is, we don't need to change all the sites that call
find_all_inheritors or find_inheritance_children in favor of new functions
that return partition OIDs in PartitionDesc order, if only *because* we
want to change planner to lock partitions in the PartitionDesc order.  I'm
failing to see why the difference in locking order matters.  I understood
the concern as that locking partitions in different order could lead to a
deadlock if concurrent backends request mutually conflicting locks, but
only one of the conflicting backends, the one that got lock on the parent,
would be allowed to lock children.  Thoughts on that?

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-09-10 23:23  David Rowley <david.rowley@2ndquadrant.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 2 replies; 265+ messages in thread

From: David Rowley @ 2018-09-10 23:23 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers

On 30 August 2018 at 21:29, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
> Attached updated patches, with 0002 containing the changes mentioned above.

Here's my first pass review on this:

0001:

1. I think the following paragraph should probably mention something
about some performance difference between the two methods:

   <para>
    Constraint exclusion works in a very similar way to partition
    pruning, except that it uses each table's <literal>CHECK</literal>
    constraints &mdash; which gives it its name &mdash; whereas partition
    pruning uses the table's partition bounds, which exist only in the
    case of declarative partitioning.  Another difference is that
    constraint exclusion is only applied at plan time; there is no attempt
    to remove partitions at execution time.
   </para>

Perhaps tagging on. "Constraint exclusion is also a much less
efficient way of eliminating unneeded partitions as metadata for each
partition must be loaded in the planner before constraint exclusion
can be applied.  This is not a requirement for partition pruning."

2. I think "rootrel" should be named "targetpart" in:

+ RelOptInfo *rootrel = root->simple_rel_array[root->parse->resultRelation];

3. Why does the following need to list_copy()?

+ List    *saved_join_info_list = list_copy(root->join_info_list);

4. Is the "root->parse->commandType != CMD_INSERT" required in:

@@ -181,13 +185,30 @@ make_one_rel(PlannerInfo *root, List *joinlist)

  /*
  * Generate access paths for the entire join tree.
+ *
+ * If we're doing this for an UPDATE or DELETE query whose target is a
+ * partitioned table, we must do the join planning against each of its
+ * leaf partitions instead.
  */
- rel = make_rel_from_joinlist(root, joinlist);
+ if (root->parse->resultRelation &&
+ root->parse->commandType != CMD_INSERT &&
+ root->simple_rel_array[root->parse->resultRelation] &&
+ root->simple_rel_array[root->parse->resultRelation]->part_scheme)
+ {

Won't the simple_rel_array entry for the resultRelation always be NULL
for an INSERT?

5. In regards to:

+ /*
+ * Hack to make the join planning code believe that 'partrel' can
+ * be joined against.
+ */
+ partrel->reloptkind = RELOPT_BASEREL;

Have you thought about other implications of join planning for other
member rels, for example, equivalence classes and em_is_child?

6. It would be good to not have to rt_fetch the same RangeTblEntry twice here:

@@ -959,7 +969,9 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
  * needs special processing, else go straight to grouping_planner.
  */
  if (parse->resultRelation &&
- rt_fetch(parse->resultRelation, parse->rtable)->inh)
+ rt_fetch(parse->resultRelation, parse->rtable)->inh &&
+ rt_fetch(parse->resultRelation, parse->rtable)->relkind !=
+ RELKIND_PARTITIONED_TABLE)
  inheritance_planner(root);

7. Why don't you just pass the Parse into the function as a parameter
instead of overwriting PlannerInfo's copy in:

+ root->parse = partition_parse;
+ partitionwise_adjust_scanjoin_target(root, child_rel,
+ subroots,
+ partitioned_rels,
+ resultRelations,
+ subpaths,
+ WCOLists,
+ returningLists,
+ rowMarks);
+ /* Restore the Query for processing the next partition. */
+ root->parse = parse;

8. Can you update the following comment to mention why you're not
calling add_paths_to_append_rel for this case?

@@ -6964,7 +7164,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root,
  }

  /* Build new paths for this relation by appending child paths. */
- if (live_children != NIL)
+ if (live_children != NIL &&
+ !(rel->reloptkind == RELOPT_BASEREL &&
+   rel->relid == root->parse->resultRelation))
  add_paths_to_append_rel(root, rel, live_children);

9. The following should use >= 0, not > 0

+ while ((relid = bms_next_member(child_rel->relids, relid)) > 0)

0002:

10. I think moving the PlannerInfo->total_table_pages calculation
needs to be done in its own patch. This is a behavioural change where
we no longer count pruned relations in the calculation which can
result in plan changes. I posted a patch in [1] to fix this as a bug
fix as I think the current code is incorrect. My patch also updates
the first paragraph of the comment. You've not done that.

11. "pruning"

+ * And do prunin.  Note that this adds AppendRelInfo's of only the

12. It's much more efficient just to do bms_add_range() outside the loop in:

+ for (i = 0; i < rel->nparts; i++)
+ {
+ rel->part_rels[i] = build_partition_rel(root, rel,
+ rel->part_oids[i]);
+ rel->live_parts = bms_add_member(rel->live_parts, i);
+ }

13. In set_append_rel_size() the foreach(l, root->append_rel_list)
loop could be made to loop over RelOptInfo->live_parts instead which
would save having to skip over AppendRelInfos that don't belong to
this parent. You'd need to make live_parts more general purpose and
also use it to mark children of inheritance parents.

14. I think you can skip the following if both are NULL. You could
likely add more smarts for different join types, but I think you
should at least skip when both are NULL. Perhaps the loop could be
driven off of bms_intersec of the two Rel's live_parts?

+ if (child_rel1 == NULL)
+ child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts);
+ if (child_rel2 == NULL)
+ child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts);

15. The following is not required when append_rel_array was previously NULL.

+ MemSet(root->append_rel_array + root->simple_rel_array_size,
+    0, sizeof(AppendRelInfo *) * num_added_parts);

16. I don't think scan_all_parts is worth the extra code.  The cost of
doing bms_num_members is going to be pretty small in comparison to
building paths for and maybe doing a join search for all partitions.

+ num_added_parts = scan_all_parts ? rel->nparts :
+ bms_num_members(partindexes);

In any case, you've got a bug in prune_append_rel_partitions() where
you're setting scan_all_parts to true instead of returning when
contradictory == true.

17. lockmode be of type LOCKMODE, not int.

+    Oid childOID, int lockmode,

18. Comment contradicts the code.

+ /* Open rel if needed; we already have required locks */
+ if (childOID != parentOID)
+ {
+ childrel = heap_open(childOID, lockmode);

I think you should be using NoLock here.

19. Comment contradicts the code.

+ /* Close child relations, but keep locks */
+ if (childOID != parentOID)
+ {
+ Assert(childrel != NULL);
+ heap_close(childrel, lockmode);
+ }

20. I assume translated_vars can now be NIL due to
build_dummy_partition_rel() not setting it.

- if (var->varlevelsup == 0 && appinfo)
+ if (var->varlevelsup == 0 && appinfo && appinfo->translated_vars)

It might be worth writing a comment to explain that, otherwise it's
not quite clear why you're doing this.

21. Unrelated change;

  Assert(relid > 0 && relid < root->simple_rel_array_size);
+

22. The following comment mentions that Oids are copied, but that does
not happen in the code.

+ /*
+ * For partitioned tables, we just allocate space for RelOptInfo's.
+ * pointers for all partitions and copy the partition OIDs from the
+ * relcache.  Actual RelOptInfo is built for a partition only if it is
+ * not pruned.
+ */

The Oid copying already happened during get_relation_info().

23. Traditionally translated_vars populated with a sequence of Vars in
the same order to mean no translation. Here you're changing how that
works:

+ /* leaving translated_vars to NIL to mean no translation needed */

This seems to be about the extent of your documentation on this, which
is not good enough.

24. "each" -> "reach"? ... Actually, I don't understand the comment.
In a partitioned hierarchy, how can the one before the top-level
partitioned table not be a partitioned table?

/*
* Keep moving up until we each the parent rel that's not a
* partitioned table.  The one before that one would be the root
* parent.
*/

25. "already"

+ * expand_inherited_rtentry alreay locked all partitions, so pass

26. Your changes to make_partitionedrel_pruneinfo() look a bit broken.
You're wrongly assuming live_parts is the same as present_parts.  If a
CHECK constraint eliminated the partition then those will be present
in live_parts but won't be part of the Append/MergeAppend subplans.
You might be able to maintain some of this optimisation by checking
for dummy rels inside the loop, but you're going to need to put back
the code that sets present_parts.

+ present_parts = bms_copy(subpart->live_parts);

27. Comment contradicts the code:

+ Bitmapset  *live_parts; /* unpruned parts; NULL if all are live */

in add_rel_partitions_to_query() you're doing:

+ if (scan_all_parts)
+ {
+ for (i = 0; i < rel->nparts; i++)
+ {
+ rel->part_rels[i] = build_partition_rel(root, rel,
+ rel->part_oids[i]);
+ rel->live_parts = bms_add_member(rel->live_parts, i);
+ }
+ }

so the NULL part seems untrue.

28. Missing comments:

+ TupleDesc tupdesc;
+ Oid reltype;

29. The comment for prune_append_rel_partitions claims it "Returns RT
indexes", but that's not the case, per:

-Relids
-prune_append_rel_partitions(RelOptInfo *rel)
+void
+prune_append_rel_partitions(PlannerInfo *root, RelOptInfo *rel)

0003:

30. 2nd test can be tested inside the first test to remove redundant
partition check.

- inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+ if (rte->relkind != RELKIND_PARTITIONED_TABLE)
+ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);

  /*
  * Check that there's at least one descendant, else treat as no-child
  * case.  This could happen despite above has_subclass() check, if table
  * once had a child but no longer does.
  */
- if (list_length(inhOIDs) < 2)
+ if (rte->relkind != RELKIND_PARTITIONED_TABLE && list_length(inhOIDs) < 2)
  {

31. The following code is wrong:

+ /* Determine the correct lockmode to use. */
+ if (rootRTindex == root->parse->resultRelation)
+ lockmode = RowExclusiveLock;
+ else if (rootrc && RowMarkRequiresRowShareLock(rootrc->markType))
+ lockmode = RowShareLock;
+ else
+ lockmode = AccessShareLock;

rootRTIndex remains at 0 if there are no row marks and resultRelation
will be 0 for SELECT queries, this means you'll use a RowExclusiveLock
for a SELECT instead of an AccessShareLock.

Why not just check the lockmode of the parent and use that?

[1] https://commitfest.postgresql.org/19/1768/

-- 
 David Rowley                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-09-11 01:11  Peter Geoghegan <pg@bowt.ie>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  6 siblings, 1 reply; 265+ messages in thread

From: Peter Geoghegan @ 2018-09-11 01:11 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers; Pavan Deolasee <pavan.deolasee@2ndquadrant.com>; Simon Riggs <simon@2ndquadrant.com>

On Wed, Aug 29, 2018 at 5:06 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> It is more or less well known that the planner doesn't perform well with
> more than a few hundred partitions even when only a handful of partitions
> are ultimately included in the plan.  Situation has improved a bit in PG
> 11 where we replaced the older method of pruning partitions one-by-one
> using constraint exclusion with a much faster method that finds relevant
> partitions by using partitioning metadata.  However, we could only use it
> for SELECT queries, because UPDATE/DELETE are handled by a completely
> different code path, whose structure doesn't allow it to call the new
> pruning module's functionality.  Actually, not being able to use the new
> pruning is not the only problem for UPDATE/DELETE, more on which further
> below.

This was a big problem for the SQL MERGE patch. I hope that this
problem can be fixed.

-- 
Peter Geoghegan




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-09-11 01:42  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Peter Geoghegan <pg@bowt.ie>
  0 siblings, 0 replies; 265+ messages in thread

From: Amit Langote @ 2018-09-11 01:42 UTC (permalink / raw)
  To: Peter Geoghegan <pg@bowt.ie>; +Cc: pgsql-hackers; Pavan Deolasee <pavan.deolasee@2ndquadrant.com>; Simon Riggs <simon@2ndquadrant.com>

On 2018/09/11 10:11, Peter Geoghegan wrote:
> On Wed, Aug 29, 2018 at 5:06 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> It is more or less well known that the planner doesn't perform well with
>> more than a few hundred partitions even when only a handful of partitions
>> are ultimately included in the plan.  Situation has improved a bit in PG
>> 11 where we replaced the older method of pruning partitions one-by-one
>> using constraint exclusion with a much faster method that finds relevant
>> partitions by using partitioning metadata.  However, we could only use it
>> for SELECT queries, because UPDATE/DELETE are handled by a completely
>> different code path, whose structure doesn't allow it to call the new
>> pruning module's functionality.  Actually, not being able to use the new
>> pruning is not the only problem for UPDATE/DELETE, more on which further
>> below.
> 
> This was a big problem for the SQL MERGE patch. I hope that this
> problem can be fixed.

Sorry if I've missed some prior discussion about this, but could you
clarify which aspect of UPDATE/DELETE planning got in the way of SQL MERGE
patch?  It'd be great if you can point to an email or portion of the SQL
MERGE discussion thread where this aspect was discussed.

In the updated patch that I'm still hacking on (also need to look at
David's comments earlier today before posting it), I have managed to
eliminate the separation of code paths handling SELECT vs UPDATE/DELETE on
inheritance tables, but I can't be sure if the new approach (also) solves
any problems that were faced during SQL MERGE development.

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-09-13 15:33  Dilip Kumar <dilipbalaut@gmail.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 0 replies; 265+ messages in thread

From: Dilip Kumar @ 2018-09-13 15:33 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers

On Tue, Sep 4, 2018 at 12:44 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> Hi Dilip,
>
> Thanks for taking a look.
>
> On 2018/09/03 20:57, Dilip Kumar wrote:
>> The idea looks interesting while going through the patch I observed
>> this comment.
>>
>> /*
>>  * inheritance_planner
>>  *   Generate Paths in the case where the result relation is an
>>  *   inheritance set.
>>  *
>>  * We have to handle this case differently from cases where a source relation
>>  * is an inheritance set. Source inheritance is expanded at the bottom of the
>>  * plan tree (see allpaths.c), but target inheritance has to be expanded at
>>  * the top.
>>
>> I think with your patch these comments needs to be change?
>
> Yes, maybe a good idea to mention that partitioned table result relations
> are not handled here.
>
> Actually, I've been wondering if this patch (0001) shouldn't get rid of
> inheritance_planner altogether and implement the new approach for *all*
> inheritance sets, not just partitioned tables, but haven't spent much time
> on that idea yet.

That will be interesting.

>
>>   if (parse->resultRelation &&
>> - rt_fetch(parse->resultRelation, parse->rtable)->inh)
>> + rt_fetch(parse->resultRelation, parse->rtable)->inh &&
>> + rt_fetch(parse->resultRelation, parse->rtable)->relkind !=
>> + RELKIND_PARTITIONED_TABLE)
>>   inheritance_planner(root);
>>   else
>>   grouping_planner(root, false, tuple_fraction);
>>
>> I think we can add some comments to explain if the target rel itself
>> is partitioned rel then why
>> we can directly go to the grouping planner.
>
> Okay, I will try to add more explanatory comments here and there in the
> next version I will post later this week.

Okay.

-- 
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-09-14 10:28  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: David Rowley <david.rowley@2ndquadrant.com>
  1 sibling, 3 replies; 265+ messages in thread

From: Amit Langote @ 2018-09-14 10:28 UTC (permalink / raw)
  To: David Rowley <david.rowley@2ndquadrant.com>; +Cc: pgsql-hackers

Thanks a lot for your detailed review.

On 2018/09/11 8:23, David Rowley wrote:
> On 30 August 2018 at 21:29, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> Attached updated patches, with 0002 containing the changes mentioned above.
> 
> Here's my first pass review on this:

I've gone through your comments on 0001 so far, but didn't go through
others yet, to which I'll reply separately.

> 0001:
>
> 1. I think the following paragraph should probably mention something
> about some performance difference between the two methods:
>
>    <para>
>     Constraint exclusion works in a very similar way to partition
>     pruning, except that it uses each table's <literal>CHECK</literal>
>     constraints &mdash; which gives it its name &mdash; whereas partition
>     pruning uses the table's partition bounds, which exist only in the
>     case of declarative partitioning.  Another difference is that
>     constraint exclusion is only applied at plan time; there is no attempt
>     to remove partitions at execution time.
>    </para>
>
> Perhaps tagging on. "Constraint exclusion is also a much less
> efficient way of eliminating unneeded partitions as metadata for each
> partition must be loaded in the planner before constraint exclusion
> can be applied.  This is not a requirement for partition pruning."

Hmm, isn't that implied by the existing text?  It already says that
constraint exclusion uses *each* table's/partition's CHECK constraints,
which should make it clear that for a whole lot of partitions, that will
be a slower than partition pruning which requires accessing only one
table's metadata.

If we will have dissociated constraint exclusion completely from
partitioned tables with these patches, I'm not sure if we have to stress
that it is inefficient for large number of tables.

> 2. I think "rootrel" should be named "targetpart" in:
>
> + RelOptInfo *rootrel = root->simple_rel_array[root->parse->resultRelation];

To me, "targetpart" makes a partitioned table sound like a partition,
which it is not.  I get that using "root" can be ambiguous, because a
query can specify a non-root partitioned table.

How about "targetrel"?

> 3. Why does the following need to list_copy()?
>
> + List    *saved_join_info_list = list_copy(root->join_info_list);

In earlier versions of this code, root->join_info_list would be modified
during make_one_rel_from_joinlist, but that no longer seems true.

Removed the list_copy.

> 4. Is the "root->parse->commandType != CMD_INSERT" required in:
>
> @@ -181,13 +185,30 @@ make_one_rel(PlannerInfo *root, List *joinlist)
>
>   /*
>   * Generate access paths for the entire join tree.
> + *
> + * If we're doing this for an UPDATE or DELETE query whose target is a
> + * partitioned table, we must do the join planning against each of its
> + * leaf partitions instead.
>   */
> - rel = make_rel_from_joinlist(root, joinlist);
> + if (root->parse->resultRelation &&
> + root->parse->commandType != CMD_INSERT &&
> + root->simple_rel_array[root->parse->resultRelation] &&
> + root->simple_rel_array[root->parse->resultRelation]->part_scheme)
> + {
>
> Won't the simple_rel_array entry for the resultRelation always be NULL
> for an INSERT?

Yep, you're right.  Removed.

> 5. In regards to:
>
> + /*
> + * Hack to make the join planning code believe that 'partrel' can
> + * be joined against.
> + */
> + partrel->reloptkind = RELOPT_BASEREL;
>
> Have you thought about other implications of join planning for other
> member rels, for example, equivalence classes and em_is_child?

Haven't really, but that seems like an important point.  I will study it
more closely.

> 6. It would be good to not have to rt_fetch the same RangeTblEntry twice
here:
>
> @@ -959,7 +969,9 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
>   * needs special processing, else go straight to grouping_planner.
>   */
>   if (parse->resultRelation &&
> - rt_fetch(parse->resultRelation, parse->rtable)->inh)
> + rt_fetch(parse->resultRelation, parse->rtable)->inh &&
> + rt_fetch(parse->resultRelation, parse->rtable)->relkind !=
> + RELKIND_PARTITIONED_TABLE)
>   inheritance_planner(root);

The new version doesn't call inheritance_planner at all; there is no
inheritance_planner in the new version.

> 7. Why don't you just pass the Parse into the function as a parameter
> instead of overwriting PlannerInfo's copy in:
>
> + root->parse = partition_parse;
> + partitionwise_adjust_scanjoin_target(root, child_rel,
> + subroots,
> + partitioned_rels,
> + resultRelations,
> + subpaths,
> + WCOLists,
> + returningLists,
> + rowMarks);
> + /* Restore the Query for processing the next partition. */
> + root->parse = parse;

Okay, done.

> 8. Can you update the following comment to mention why you're not
> calling add_paths_to_append_rel for this case?
>
> @@ -6964,7 +7164,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root,
>   }
>
>   /* Build new paths for this relation by appending child paths. */
> - if (live_children != NIL)
> + if (live_children != NIL &&
> + !(rel->reloptkind == RELOPT_BASEREL &&
> +   rel->relid == root->parse->resultRelation))
>   add_paths_to_append_rel(root, rel, live_children);

Oops, stray code from a previous revision.  Removed this hunk.

>
> 9. The following should use >= 0, not > 0
>
> + while ((relid = bms_next_member(child_rel->relids, relid)) > 0)

Yeah, fixed.


Sorry, I haven't yet worked on your comments on 0002 and 0003.  For time
being, I'd like to report what I've been up to these past couple of days,
because starting tomorrow until the end of this month, I won't be able to
reply to emails on -hackers due to personal vacation.

So, I spent a better part of last few days on trying to revise the patches
so that it changes the planning code for *all* inheritance cases instead
of just focusing on partitioning.  Because, really, the only difference
between the partitioning code and regular inheritance code should be that
partitioning is able to use faster pruning, and all the other parts should
look and work more or less the same.  There shouldn't be code here that
deals with partitioning and code there to deal with inheritance.
Minimizing code this way should be a good end to aim for, imho.

Attached is what I have at the moment.  As part of the effort mentioned
above, I made 0001 to remove inheritance_planner altogether, instead of
just taking out the partitioning case out of it.  Then there is the WIP
patch 0004 which tries to move even regular inheritance expansion to late
planning stage, just like the earlier versions did for partitioning.  It
will need quite a bit of polishing before we could consider it for merging
with 0002.  Of course, I'll need to address your comments before
considering doing that any seriously.

Thanks,
Amit

Attachments: download all (4 files, .tar.gz)

v3-0001-Overhaul-inheritance-update-delete-planning.patch (text/plain, 59.9K) download From d23d4740b6a0eb533205f8c94e3509d23609991e Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 24 Aug 2018 12:39:36 +0900 Subject: [PATCH v3 1/4] Overhaul inheritance update/delete planning Current method, inheritance_planner, applies grouping_planner and hence query_planner to the query repeatedly with each child relation replacing the root parent as the query's result relation. One big drawback of this approach is that it cannot use partprune.c to perform partition pruning when the root parent is a partitioned table, because it can only be invoked if query_planner sees the partitioned relation itself in the query. That is not true with how inheritance_planner invokes query_planner, which it does after replacing the partitioned relation with individual leaf partitions. While most of the work in each repitition of grouping_planner (and query_planner) is same, a couple of things may differ from one child relation to another -- 1. Join planning may produce different Paths when joining against different child result partitions, 2. grouping_planner may produce different top-level target lists for different child relations, based on their own TupleDescs. This commit removes inheritance_planner in favor of rearranging things such that, only the planning steps that affect 1 and 2 above are repeated for unpruned child relations. --- doc/src/sgml/ddl.sgml | 15 +- src/backend/nodes/outfuncs.c | 1 - src/backend/optimizer/path/allpaths.c | 224 +++++- src/backend/optimizer/plan/createplan.c | 10 - src/backend/optimizer/plan/planmain.c | 4 +- src/backend/optimizer/plan/planner.c | 1018 ++++++++++---------------- src/backend/optimizer/prep/prepjointree.c | 1 - src/backend/optimizer/prep/prepunion.c | 28 +- src/backend/optimizer/util/plancat.c | 50 +- src/include/nodes/relation.h | 6 +- src/test/regress/expected/partition_join.out | 4 +- 11 files changed, 631 insertions(+), 730 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index b5ed1b7939..53c479fbb8 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -3933,16 +3933,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <xref linkend="guc-enable-partition-pruning"/> setting. </para> - <note> - <para> - Currently, pruning of partitions during the planning of an - <command>UPDATE</command> or <command>DELETE</command> command is - implemented using the constraint exclusion method (however, it is - controlled by the <literal>enable_partition_pruning</literal> rather than - <literal>constraint_exclusion</literal>) &mdash; see the following section - for details and caveats that apply. - </para> - <para> Execution-time partition pruning currently occurs for the <literal>Append</literal> and <literal>MergeAppend</literal> node types. @@ -3964,9 +3954,8 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <para> <firstterm>Constraint exclusion</firstterm> is a query optimization - technique similar to partition pruning. While it is primarily used - for partitioning implemented using the legacy inheritance method, it can be - used for other purposes, including with declarative partitioning. + technique similar to partition pruning. It is primarily used + for partitioning implemented using the legacy inheritance method. </para> <para> diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index b5af904c18..3f2339a3ea 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2309,7 +2309,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); WRITE_UINT_FIELD(qual_security_level); - WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind); WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasDeletedRTEs); diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 5db1688bf0..968cac42a3 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -36,6 +36,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" @@ -119,6 +120,9 @@ static void set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); +static RelOptInfo *inheritance_make_rel_from_joinlist(PlannerInfo *root, + RelOptInfo *parent, + List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, pushdown_safety_info *safetyInfo); static bool recurse_pushdown_safe(Node *setOp, Query *topquery, @@ -181,13 +185,27 @@ make_one_rel(PlannerInfo *root, List *joinlist) /* * Generate access paths for the entire join tree. + * + * For UPDATE/DELETE on an inheritance parent, join paths should be + * generated for each child result rel separately. */ - rel = make_rel_from_joinlist(root, joinlist); + if (root->parse->resultRelation && + root->simple_rte_array[root->parse->resultRelation]->inh) + { + RelOptInfo *targetrel = root->simple_rel_array[root->parse->resultRelation]; - /* - * The result should join all and only the query's base rels. - */ - Assert(bms_equal(rel->relids, root->all_baserels)); + rel = inheritance_make_rel_from_joinlist(root, targetrel, joinlist); + } + else + { + rel = make_rel_from_joinlist(root, joinlist); + + /* + * The result should join all and only the query's base rels. + */ + Assert(bms_equal(rel->relids, root->all_baserels)); + + } return rel; } @@ -868,7 +886,11 @@ static void set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { + Relids top_parent_relids = rel->top_parent_relids; int parentRTindex = rti; + int rootParentRTindex = top_parent_relids ? + bms_singleton_member(top_parent_relids) : + parentRTindex; bool has_live_children; double parent_rows; double parent_size; @@ -968,23 +990,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); /* - * Copy/Modify targetlist. Even if this child is deemed empty, we need - * its targetlist in case it falls on nullable side in a child-join - * because of partitionwise join. - * - * NB: the resulting childrel->reltarget->exprs may contain arbitrary - * expressions, which otherwise would not occur in a rel's targetlist. - * Code that might be looking at an appendrel child must cope with - * such. (Normally, a rel's targetlist would only include Vars and - * PlaceHolderVars.) XXX we do not bother to update the cost or width - * fields of childrel->reltarget; not clear if that would be useful. - */ - childrel->reltarget->exprs = (List *) - adjust_appendrel_attrs(root, - (Node *) rel->reltarget->exprs, - 1, &appinfo); - - /* * We have to make child entries in the EquivalenceClass data * structures as well. This is needed either if the parent * participates in some eclass joins (because we will want to consider @@ -1110,9 +1115,28 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childrel->baserestrictinfo = childquals; childrel->baserestrict_min_security = cq_min_security; - if (have_const_false_cq) + /* + * Mark the child as empty in certain cases, such as if a restriction + * clause reduced to constant FALSE or NULL, or if the partition + * of a partitioned table has been pruned, or if constaint exclusion + * pruned it. + */ + if (have_const_false_cq || + (did_pruning && + !bms_is_member(appinfo->child_relid, live_children)) || + relation_excluded_by_constraints(root, childrel, childRTE)) { /* + * The child relation won't be scanned, but certain partitionwise + * planning operations require its reltarget to contain valid + * expressions, so oblige. + */ + childrel->reltarget->exprs = (List *) + adjust_appendrel_attrs(root, + (Node *) rel->reltarget->exprs, + 1, &appinfo); + + /* * Some restriction clause reduced to constant FALSE or NULL after * substitution, so this child need not be scanned. */ @@ -1120,22 +1144,56 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, continue; } - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) + /* + * Copy/Modify targetlist. Even if this child is deemed empty, we need + * its targetlist in case it falls on nullable side in a child-join + * because of partitionwise join. + * + * NB: the resulting childrel->reltarget->exprs may contain arbitrary + * expressions, which otherwise would not occur in a rel's targetlist. + * Code that might be looking at an appendrel child must cope with + * such. (Normally, a rel's targetlist would only include Vars and + * PlaceHolderVars.) XXX we do not bother to update the cost or width + * fields of childrel->reltarget; not clear if that would be useful. + */ + if (rootParentRTindex == root->parse->resultRelation) { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } + Query *parse, + *orig_parse = root->parse; + List *tlist; + List *other_exprs; + ListCell *lc2; + + parse = (Query *) + adjust_appendrel_attrs(root, + (Node *) root->parse, + 1, &appinfo); + root->parse = parse; + tlist = preprocess_targetlist(root); + build_base_rel_tlists(root, tlist); + root->parse = orig_parse; - if (relation_excluded_by_constraints(root, childrel, childRTE)) - { /* - * This child need not be scanned, so we can omit it from the - * appendrel. + * Now add members of parent's reltarget->exprs, not already in + * child's. */ - set_dummy_rel_pathlist(childrel); - continue; + other_exprs = (List *) + adjust_appendrel_attrs(root, + (Node *) rel->reltarget->exprs, + 1, &appinfo); + foreach(lc2, other_exprs) + { + if (!list_member(childrel->reltarget->exprs, lfirst(lc2))) + childrel->reltarget->exprs = + lappend(childrel->reltarget->exprs, + lfirst(lc2)); + } } + else + childrel->reltarget->exprs = (List *) + adjust_appendrel_attrs(root, + (Node *) rel->reltarget->exprs, + 1, &appinfo); /* CE failed, so finish copying/modifying join quals. */ childrel->joininfo = (List *) @@ -2570,6 +2628,104 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows) } /* + * inheritance_make_rel_from_joinlist + * Invokes make_rel_from_joinlist for each parent's child relations + * + * This function is called recursively if a child relation is itself a + * partitioned table. + */ +static RelOptInfo * +inheritance_make_rel_from_joinlist(PlannerInfo *root, + RelOptInfo *parent, + List *joinlist) +{ + ListCell *lc; + + foreach(lc, root->append_rel_list) + { + RelOptInfo *childrel; + AppendRelInfo *appinfo = lfirst(lc); + List *translated_joinlist; + List *saved_join_info_list = root->join_info_list; + + /* Only consider this parent's children. */ + if (appinfo->parent_relid != parent->relid) + continue; + + childrel = find_base_rel(root, appinfo->child_relid); + + /* Ignore excluded/pruned children. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* + * Modify joinlist such that relations joined to parent rel appear to + * be joined to child rel instead. + */ + translated_joinlist = (List *) + adjust_appendrel_attrs(root, + (Node *) joinlist, + 1, &appinfo); + + /* + * Since we modified the join tree to include child rel, we can + * modify it to be a "base" rel instead of an "other" rel, which + * join planning code expects the callers to do anyway. + */ + childrel->reloptkind = RELOPT_BASEREL; + root->join_info_list = (List *) + adjust_appendrel_attrs(root, + (Node *) root->join_info_list, + 1, &appinfo); + + /* + * Recurse if the child is itself a partitioned table. We must, + * because grandchildren can only be processed this way. + */ + if (childrel->part_scheme != NULL) + childrel = + inheritance_make_rel_from_joinlist(root, childrel, + translated_joinlist); + else + { +#ifdef USE_ASSERT_CHECKING + Relids all_baserels; + Index top_parent_relid; +#endif + + /* Reset interal join planning data structures. */ + root->join_rel_list = NIL; + root->join_rel_hash = NULL; + + /* Perform join planning and save the resulting joinrel. */ + childrel = make_rel_from_joinlist(root, translated_joinlist); + +#ifdef USE_ASSERT_CHECKING + all_baserels = bms_copy(root->all_baserels); + top_parent_relid = parent->top_parent_relids ? + bms_singleton_member(parent->top_parent_relids) : + parent->relid; + all_baserels = bms_del_member(all_baserels, top_parent_relid); + all_baserels = bms_add_member(all_baserels, appinfo->child_relid); + Assert(bms_equal(childrel->relids, all_baserels)); +#endif + } + + /* + * Save child joinrel to be processed later in + * inheritance_adjust_scanjoin_target, which adjusts its paths to + * be able to emit expressions in query's top-level target list. + */ + root->inh_target_child_rels = lappend(root->inh_target_child_rels, + childrel); + + root->join_info_list = saved_join_info_list; + } + + return parent; +} + +/* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. * diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index ae41c9efa0..9e3b62ba1c 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -2003,12 +2003,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) /* * During setrefs.c, we'll need the grouping_map to fix up the cols lists * in GroupingFunc nodes. Save it for setrefs.c to use. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be grouping in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->grouping_map == NULL); root->grouping_map = grouping_map; @@ -2165,12 +2160,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) * with InitPlan output params. (We can't just do that locally in the * MinMaxAgg node, because path nodes above here may have Agg references * as well.) Save the mmaggregates list to tell setrefs.c to do that. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be aggregates in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->minmax_aggs == NIL); root->minmax_aggs = best_path->mmaggregates; diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index b05adc70c4..3f0d80eaa6 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -266,7 +266,9 @@ query_planner(PlannerInfo *root, List *tlist, /* Check that we got at least one usable path */ if (!final_rel || !final_rel->cheapest_total_path || - final_rel->cheapest_total_path->param_info != NULL) + final_rel->cheapest_total_path->param_info != NULL || + (final_rel->relid == root->parse->resultRelation && + root->parse->commandType == CMD_INSERT)) elog(ERROR, "failed to construct the join relation"); return final_rel; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 96bf0601a8..9636d7a4ee 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -113,9 +113,7 @@ typedef struct /* Local functions */ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); -static void inheritance_planner(PlannerInfo *root); -static void grouping_planner(PlannerInfo *root, bool inheritance_update, - double tuple_fraction); +static void grouping_planner(PlannerInfo *root, double tuple_fraction); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, int *tleref_to_colnum_map); @@ -238,6 +236,20 @@ static bool group_by_has_partkey(RelOptInfo *input_rel, List *targetList, List *groupClause); +static void inheritance_adjust_scanjoin_target(PlannerInfo *root, + Query *parse, + RelOptInfo *parent, + List **partition_subroots, + Index *nominalRelation, + List **partitioned_rels, + List **resultRelations, + List **subpaths, + List **WCOLists, + List **returningLists, + List **rowMarks); +static void adjust_child_reltarget(PlannerInfo *root, Relids where_needed, + List *tlist); + /***************************************************************************** * @@ -622,7 +634,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->grouping_map = NULL; root->minmax_aggs = NIL; root->qual_security_level = 0; - root->inhTargetKind = INHKIND_NONE; root->hasRecursion = hasRecursion; if (hasRecursion) root->wt_param_id = SS_assign_special_param(root); @@ -954,15 +965,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse, if (hasOuterJoins) reduce_outer_joins(root); - /* - * Do the main planning. If we have an inherited target relation, that - * needs special processing, else go straight to grouping_planner. - */ - if (parse->resultRelation && - rt_fetch(parse->resultRelation, parse->rtable)->inh) - inheritance_planner(root); - else - grouping_planner(root, false, tuple_fraction); + /* Do the main planning. */ + grouping_planner(root, tuple_fraction); /* * Capture the set of outer-level param IDs we have access to, for use in @@ -1132,517 +1136,6 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr) return (Expr *) preprocess_expression(root, (Node *) expr, EXPRKIND_PHV); } -/* - * inheritance_planner - * Generate Paths in the case where the result relation is an - * inheritance set. - * - * We have to handle this case differently from cases where a source relation - * is an inheritance set. Source inheritance is expanded at the bottom of the - * plan tree (see allpaths.c), but target inheritance has to be expanded at - * the top. The reason is that for UPDATE, each target relation needs a - * different targetlist matching its own column set. Fortunately, - * the UPDATE/DELETE target can never be the nullable side of an outer join, - * so it's OK to generate the plan this way. - * - * Returns nothing; the useful output is in the Paths we attach to - * the (UPPERREL_FINAL, NULL) upperrel stored in *root. - * - * Note that we have not done set_cheapest() on the final rel; it's convenient - * to leave this to the caller. - */ -static void -inheritance_planner(PlannerInfo *root) -{ - Query *parse = root->parse; - int top_parentRTindex = parse->resultRelation; - Bitmapset *subqueryRTindexes; - Bitmapset *modifiableARIindexes; - int nominalRelation = -1; - List *final_rtable = NIL; - int save_rel_array_size = 0; - RelOptInfo **save_rel_array = NULL; - AppendRelInfo **save_append_rel_array = NULL; - List *subpaths = NIL; - List *subroots = NIL; - List *resultRelations = NIL; - List *withCheckOptionLists = NIL; - List *returningLists = NIL; - List *rowMarks; - RelOptInfo *final_rel; - ListCell *lc; - Index rti; - RangeTblEntry *parent_rte; - Relids partitioned_relids = NULL; - List *partitioned_rels = NIL; - PlannerInfo *parent_root; - Query *parent_parse; - Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); - PlannerInfo **parent_roots = NULL; - - Assert(parse->commandType != CMD_INSERT); - - /* - * We generate a modified instance of the original Query for each target - * relation, plan that, and put all the plans into a list that will be - * controlled by a single ModifyTable node. All the instances share the - * same rangetable, but each instance must have its own set of subquery - * RTEs within the finished rangetable because (1) they are likely to get - * scribbled on during planning, and (2) it's not inconceivable that - * subqueries could get planned differently in different cases. We need - * not create duplicate copies of other RTE kinds, in particular not the - * target relations, because they don't have either of those issues. Not - * having to duplicate the target relations is important because doing so - * (1) would result in a rangetable of length O(N^2) for N targets, with - * at least O(N^3) work expended here; and (2) would greatly complicate - * management of the rowMarks list. - * - * To begin with, generate a bitmapset of the relids of the subquery RTEs. - */ - subqueryRTindexes = NULL; - rti = 1; - foreach(lc, parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - if (rte->rtekind == RTE_SUBQUERY) - subqueryRTindexes = bms_add_member(subqueryRTindexes, rti); - rti++; - } - - /* - * Next, we want to identify which AppendRelInfo items contain references - * to any of the aforesaid subquery RTEs. These items will need to be - * copied and modified to adjust their subquery references; whereas the - * other ones need not be touched. It's worth being tense over this - * because we can usually avoid processing most of the AppendRelInfo - * items, thereby saving O(N^2) space and time when the target is a large - * inheritance tree. We can identify AppendRelInfo items by their - * child_relid, since that should be unique within the list. - */ - modifiableARIindexes = NULL; - if (subqueryRTindexes != NULL) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - - if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) || - bms_is_member(appinfo->child_relid, subqueryRTindexes) || - bms_overlap(pull_varnos((Node *) appinfo->translated_vars), - subqueryRTindexes)) - modifiableARIindexes = bms_add_member(modifiableARIindexes, - appinfo->child_relid); - } - } - - /* - * If the parent RTE is a partitioned table, we should use that as the - * nominal relation, because the RTEs added for partitioned tables - * (including the root parent) as child members of the inheritance set do - * not appear anywhere else in the plan. The situation is exactly the - * opposite in the case of non-partitioned inheritance parent as described - * below. For the same reason, collect the list of descendant partitioned - * tables to be saved in ModifyTable node, so that executor can lock those - * as well. - */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); - if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) - { - nominalRelation = top_parentRTindex; - - /* - * Root parent's RT index is always present in the partitioned_rels of - * the ModifyTable node, if one is needed at all. - */ - partitioned_relids = bms_make_singleton(top_parentRTindex); - } - - /* - * The PlannerInfo for each child is obtained by translating the relevant - * members of the PlannerInfo for its immediate parent, which we find - * using the parent_relid in its AppendRelInfo. We save the PlannerInfo - * for each parent in an array indexed by relid for fast retrieval. Since - * the maximum number of parents is limited by the number of RTEs in the - * query, we use that number to allocate the array. An extra entry is - * needed since relids start from 1. - */ - parent_roots = (PlannerInfo **) palloc0((list_length(parse->rtable) + 1) * - sizeof(PlannerInfo *)); - parent_roots[top_parentRTindex] = root; - - /* - * And now we can get on with generating a plan for each child table. - */ - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - PlannerInfo *subroot; - RangeTblEntry *child_rte; - RelOptInfo *sub_final_rel; - Path *subpath; - - /* append_rel_list contains all append rels; ignore others */ - if (!bms_is_member(appinfo->parent_relid, parent_relids)) - continue; - - /* - * expand_inherited_rtentry() always processes a parent before any of - * that parent's children, so the parent_root for this relation should - * already be available. - */ - parent_root = parent_roots[appinfo->parent_relid]; - Assert(parent_root != NULL); - parent_parse = parent_root->parse; - - /* - * We need a working copy of the PlannerInfo so that we can control - * propagation of information back to the main copy. - */ - subroot = makeNode(PlannerInfo); - memcpy(subroot, parent_root, sizeof(PlannerInfo)); - - /* - * Generate modified query with this rel as target. We first apply - * adjust_appendrel_attrs, which copies the Query and changes - * references to the parent RTE to refer to the current child RTE, - * then fool around with subquery RTEs. - */ - subroot->parse = (Query *) - adjust_appendrel_attrs(parent_root, - (Node *) parent_parse, - 1, &appinfo); - - /* - * If there are securityQuals attached to the parent, move them to the - * child rel (they've already been transformed properly for that). - */ - parent_rte = rt_fetch(appinfo->parent_relid, subroot->parse->rtable); - child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable); - child_rte->securityQuals = parent_rte->securityQuals; - parent_rte->securityQuals = NIL; - - /* - * Mark whether we're planning a query to a partitioned table or an - * inheritance parent. - */ - subroot->inhTargetKind = - partitioned_relids ? INHKIND_PARTITIONED : INHKIND_INHERITED; - - /* - * If this child is further partitioned, remember it as a parent. - * Since a partitioned table does not have any data, we don't need to - * create a plan for it, and we can stop processing it here. We do, - * however, need to remember its modified PlannerInfo for use when - * processing its children, since we'll update their varnos based on - * the delta from immediate parent to child, not from top to child. - * - * Note: a very non-obvious point is that we have not yet added - * duplicate subquery RTEs to the subroot's rtable. We mustn't, - * because then its children would have two sets of duplicates, - * confusing matters. - */ - if (child_rte->inh) - { - Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE); - parent_relids = bms_add_member(parent_relids, appinfo->child_relid); - parent_roots[appinfo->child_relid] = subroot; - - continue; - } - - /* - * Set the nominal target relation of the ModifyTable node if not - * already done. We use the inheritance parent RTE as the nominal - * target relation if it's a partitioned table (see just above this - * loop). In the non-partitioned parent case, we'll use the first - * child relation (even if it's excluded) as the nominal target - * relation. Because of the way expand_inherited_rtentry works, the - * latter should be the RTE representing the parent table in its role - * as a simple member of the inheritance set. - * - * It would be logically cleaner to *always* use the inheritance - * parent RTE as the nominal relation; but that RTE is not otherwise - * referenced in the plan in the non-partitioned inheritance case. - * Instead the duplicate child RTE created by expand_inherited_rtentry - * is used elsewhere in the plan, so using the original parent RTE - * would give rise to confusing use of multiple aliases in EXPLAIN - * output for what the user will think is the "same" table. OTOH, - * it's not a problem in the partitioned inheritance case, because the - * duplicate child RTE added for the parent does not appear anywhere - * else in the plan tree. - */ - if (nominalRelation < 0) - nominalRelation = appinfo->child_relid; - - /* - * The rowMarks list might contain references to subquery RTEs, so - * make a copy that we can apply ChangeVarNodes to. (Fortunately, the - * executor doesn't need to see the modified copies --- we can just - * pass it the original rowMarks list.) - */ - subroot->rowMarks = copyObject(parent_root->rowMarks); - - /* - * The append_rel_list likewise might contain references to subquery - * RTEs (if any subqueries were flattenable UNION ALLs). So prepare - * to apply ChangeVarNodes to that, too. As explained above, we only - * want to copy items that actually contain such references; the rest - * can just get linked into the subroot's append_rel_list. - * - * If we know there are no such references, we can just use the outer - * append_rel_list unmodified. - */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - subroot->append_rel_list = NIL; - foreach(lc2, parent_root->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, modifiableARIindexes)) - appinfo2 = copyObject(appinfo2); - - subroot->append_rel_list = lappend(subroot->append_rel_list, - appinfo2); - } - } - - /* - * Add placeholders to the child Query's rangetable list to fill the - * RT indexes already reserved for subqueries in previous children. - * These won't be referenced, so there's no need to make them very - * valid-looking. - */ - while (list_length(subroot->parse->rtable) < list_length(final_rtable)) - subroot->parse->rtable = lappend(subroot->parse->rtable, - makeNode(RangeTblEntry)); - - /* - * If this isn't the first child Query, generate duplicates of all - * subquery RTEs, and adjust Var numbering to reference the - * duplicates. To simplify the loop logic, we scan the original rtable - * not the copy just made by adjust_appendrel_attrs; that should be OK - * since subquery RTEs couldn't contain any references to the target - * rel. - */ - if (final_rtable != NIL && subqueryRTindexes != NULL) - { - ListCell *lr; - - rti = 1; - foreach(lr, parent_parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lr); - - if (bms_is_member(rti, subqueryRTindexes)) - { - Index newrti; - - /* - * The RTE can't contain any references to its own RT - * index, except in its securityQuals, so we can save a - * few cycles by applying ChangeVarNodes to the rest of - * the rangetable before we append the RTE to it. - */ - newrti = list_length(subroot->parse->rtable) + 1; - ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); - ChangeVarNodes((Node *) subroot->rowMarks, rti, newrti, 0); - /* Skip processing unchanging parts of append_rel_list */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - foreach(lc2, subroot->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, - modifiableARIindexes)) - ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); - } - } - rte = copyObject(rte); - ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); - subroot->parse->rtable = lappend(subroot->parse->rtable, - rte); - } - rti++; - } - } - - /* There shouldn't be any OJ info to translate, as yet */ - Assert(subroot->join_info_list == NIL); - /* and we haven't created PlaceHolderInfos, either */ - Assert(subroot->placeholder_list == NIL); - - /* Generate Path(s) for accessing this result relation */ - grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ ); - - /* - * Select cheapest path in case there's more than one. We always run - * modification queries to conclusion, so we care only for the - * cheapest-total path. - */ - sub_final_rel = fetch_upper_rel(subroot, UPPERREL_FINAL, NULL); - set_cheapest(sub_final_rel); - subpath = sub_final_rel->cheapest_total_path; - - /* - * If this child rel was excluded by constraint exclusion, exclude it - * from the result plan. - */ - if (IS_DUMMY_PATH(subpath)) - continue; - - /* - * Add the current parent's RT index to the partitioned_relids set if - * we're creating the ModifyTable path for a partitioned root table. - * (We only care about parents of non-excluded children.) - */ - if (partitioned_relids) - partitioned_relids = bms_add_member(partitioned_relids, - appinfo->parent_relid); - - /* - * If this is the first non-excluded child, its post-planning rtable - * becomes the initial contents of final_rtable; otherwise, append - * just its modified subquery RTEs to final_rtable. - */ - if (final_rtable == NIL) - final_rtable = subroot->parse->rtable; - else - final_rtable = list_concat(final_rtable, - list_copy_tail(subroot->parse->rtable, - list_length(final_rtable))); - - /* - * We need to collect all the RelOptInfos from all child plans into - * the main PlannerInfo, since setrefs.c will need them. We use the - * last child's simple_rel_array (previous ones are too short), so we - * have to propagate forward the RelOptInfos that were already built - * in previous children. - */ - Assert(subroot->simple_rel_array_size >= save_rel_array_size); - for (rti = 1; rti < save_rel_array_size; rti++) - { - RelOptInfo *brel = save_rel_array[rti]; - - if (brel) - subroot->simple_rel_array[rti] = brel; - } - save_rel_array_size = subroot->simple_rel_array_size; - save_rel_array = subroot->simple_rel_array; - save_append_rel_array = subroot->append_rel_array; - - /* Make sure any initplans from this rel get into the outer list */ - root->init_plans = subroot->init_plans; - - /* Build list of sub-paths */ - subpaths = lappend(subpaths, subpath); - - /* Build list of modified subroots, too */ - subroots = lappend(subroots, subroot); - - /* Build list of target-relation RT indexes */ - resultRelations = lappend_int(resultRelations, appinfo->child_relid); - - /* Build lists of per-relation WCO and RETURNING targetlists */ - if (parse->withCheckOptions) - withCheckOptionLists = lappend(withCheckOptionLists, - subroot->parse->withCheckOptions); - if (parse->returningList) - returningLists = lappend(returningLists, - subroot->parse->returningList); - - Assert(!parse->onConflict); - } - - /* Result path must go into outer query's FINAL upperrel */ - final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL); - - /* - * We don't currently worry about setting final_rel's consider_parallel - * flag in this case, nor about allowing FDWs or create_upper_paths_hook - * to get control here. - */ - - /* - * If we managed to exclude every child rel, return a dummy plan; it - * doesn't even need a ModifyTable node. - */ - if (subpaths == NIL) - { - set_dummy_rel_pathlist(final_rel); - return; - } - - /* - * Put back the final adjusted rtable into the master copy of the Query. - * (We mustn't do this if we found no non-excluded children.) - */ - parse->rtable = final_rtable; - root->simple_rel_array_size = save_rel_array_size; - root->simple_rel_array = save_rel_array; - root->append_rel_array = save_append_rel_array; - - /* Must reconstruct master's simple_rte_array, too */ - root->simple_rte_array = (RangeTblEntry **) - palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *)); - rti = 1; - foreach(lc, final_rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - root->simple_rte_array[rti++] = rte; - } - - /* - * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will - * have dealt with fetching non-locked marked rows, else we need to have - * ModifyTable do that. - */ - if (parse->rowMarks) - rowMarks = NIL; - else - rowMarks = root->rowMarks; - - if (partitioned_relids) - { - int i; - - i = -1; - while ((i = bms_next_member(partitioned_relids, i)) >= 0) - partitioned_rels = lappend_int(partitioned_rels, i); - - /* - * If we're going to create ModifyTable at all, the list should - * contain at least one member, that is, the root parent's index. - */ - Assert(list_length(partitioned_rels) >= 1); - partitioned_rels = list_make1(partitioned_rels); - } - - /* Create Path representing a ModifyTable to do the UPDATE/DELETE work */ - add_path(final_rel, (Path *) - create_modifytable_path(root, final_rel, - parse->commandType, - parse->canSetTag, - nominalRelation, - partitioned_rels, - root->partColsUpdated, - resultRelations, - subpaths, - subroots, - withCheckOptionLists, - returningLists, - rowMarks, - NULL, - SS_assign_special_param(root))); -} - /*-------------------- * grouping_planner * Perform planning steps related to grouping, aggregation, etc. @@ -1650,11 +1143,6 @@ inheritance_planner(PlannerInfo *root) * This function adds all required top-level processing to the scan/join * Path(s) produced by query_planner. * - * If inheritance_update is true, we're being called from inheritance_planner - * and should not include a ModifyTable step in the resulting Path(s). - * (inheritance_planner will create a single ModifyTable node covering all the - * target tables.) - * * tuple_fraction is the fraction of tuples we expect will be retrieved. * tuple_fraction is interpreted as follows: * 0: expect all tuples to be retrieved (normal case) @@ -1672,8 +1160,7 @@ inheritance_planner(PlannerInfo *root) *-------------------- */ static void -grouping_planner(PlannerInfo *root, bool inheritance_update, - double tuple_fraction) +grouping_planner(PlannerInfo *root, double tuple_fraction) { Query *parse = root->parse; List *tlist; @@ -1688,6 +1175,15 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, RelOptInfo *current_rel; RelOptInfo *final_rel; ListCell *lc; + List *orig_parse_tlist = list_copy(parse->targetList); + Index nominalRelation = 0; + List *partition_subroots = NIL; + List *partitioned_rels = NIL; + List *partition_resultRelations = NIL; + List *partition_subpaths = NIL; + List *partition_WCOLists = NIL; + List *partition_returningLists = NIL; + List *partition_rowMarks = NIL; /* Tweak caller-supplied tuple_fraction if have LIMIT/OFFSET */ if (parse->limitCount || parse->limitOffset) @@ -2018,13 +1514,45 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, scanjoin_targets_contain_srfs = NIL; } - /* Apply scan/join target. */ - scanjoin_target_same_exprs = list_length(scanjoin_targets) == 1 - && equal(scanjoin_target->exprs, current_rel->reltarget->exprs); - apply_scanjoin_target_to_paths(root, current_rel, scanjoin_targets, - scanjoin_targets_contain_srfs, - scanjoin_target_parallel_safe, - scanjoin_target_same_exprs); + /* + * For UPDATE/DELETE on an inheritance parent, we must generate and + * apply scanjoin target based on targetlist computed using each + * of the child relations. + */ + if (parse->commandType != CMD_INSERT && + current_rel->reloptkind == RELOPT_BASEREL && + current_rel->relid == root->parse->resultRelation && + root->simple_rte_array[current_rel->relid]->inh) + { + /* + * scanjoin_target shouldn't have changed from final_target, + * because UPDATE/DELETE doesn't support various features that + * would've required modifications that are performed above. + * That's important because we'll generate final_target freshly + * for each partition in inheritance_adjust_scanjoin_target. + */ + Assert(scanjoin_target == final_target); + root->parse->targetList = orig_parse_tlist; + inheritance_adjust_scanjoin_target(root, root->parse, current_rel, + &partition_subroots, + &nominalRelation, + &partitioned_rels, + &partition_resultRelations, + &partition_subpaths, + &partition_WCOLists, + &partition_returningLists, + &partition_rowMarks); + } + else + { + /* Apply scan/join target. */ + scanjoin_target_same_exprs = list_length(scanjoin_targets) == 1 + && equal(scanjoin_target->exprs, current_rel->reltarget->exprs); + apply_scanjoin_target_to_paths(root, current_rel, scanjoin_targets, + scanjoin_targets_contain_srfs, + scanjoin_target_parallel_safe, + scanjoin_target_same_exprs); + } /* * Save the various upper-rel PathTargets we just computed into @@ -2137,92 +1665,123 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, final_rel->fdwroutine = current_rel->fdwroutine; /* - * Generate paths for the final_rel. Insert all surviving paths, with - * LockRows, Limit, and/or ModifyTable steps added if needed. + * If the table has partitions, but all of them were pruned, then + * nothing to do. */ - foreach(lc, current_rel->pathlist) + if (current_rel->reloptkind == RELOPT_BASEREL && + current_rel->relid == root->parse->resultRelation && + !IS_DUMMY_REL(current_rel) && + root->simple_rte_array[current_rel->relid]->inh && + parse->commandType != CMD_INSERT) { - Path *path = (Path *) lfirst(lc); - - /* - * If there is a FOR [KEY] UPDATE/SHARE clause, add the LockRows node. - * (Note: we intentionally test parse->rowMarks not root->rowMarks - * here. If there are only non-locking rowmarks, they should be - * handled by the ModifyTable node instead. However, root->rowMarks - * is what goes into the LockRows node.) - */ - if (parse->rowMarks) - { - path = (Path *) create_lockrows_path(root, final_rel, path, - root->rowMarks, - SS_assign_special_param(root)); - } - - /* - * If there is a LIMIT/OFFSET clause, add the LIMIT node. - */ - if (limit_needed(parse)) - { - path = (Path *) create_limit_path(root, final_rel, path, - parse->limitOffset, - parse->limitCount, - offset_est, count_est); - } - - /* - * If this is an INSERT/UPDATE/DELETE, and we're not being called from - * inheritance_planner, add the ModifyTable node. - */ - if (parse->commandType != CMD_SELECT && !inheritance_update) - { - List *withCheckOptionLists; - List *returningLists; - List *rowMarks; - - /* - * Set up the WITH CHECK OPTION and RETURNING lists-of-lists, if - * needed. - */ - if (parse->withCheckOptions) - withCheckOptionLists = list_make1(parse->withCheckOptions); - else - withCheckOptionLists = NIL; - - if (parse->returningList) - returningLists = list_make1(parse->returningList); - else - returningLists = NIL; - - /* - * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node - * will have dealt with fetching non-locked marked rows, else we - * need to have ModifyTable do that. - */ - if (parse->rowMarks) - rowMarks = NIL; - else - rowMarks = root->rowMarks; - - path = (Path *) + Path *path = (Path *) create_modifytable_path(root, final_rel, parse->commandType, parse->canSetTag, - parse->resultRelation, - NIL, - false, - list_make1_int(parse->resultRelation), - list_make1(path), - list_make1(root), - withCheckOptionLists, - returningLists, - rowMarks, - parse->onConflict, + nominalRelation, + partitioned_rels, + root->partColsUpdated, + partition_resultRelations, + partition_subpaths, + partition_subroots, + partition_WCOLists, + partition_returningLists, + partition_rowMarks, + NULL, SS_assign_special_param(root)); - } - - /* And shove it into final_rel */ add_path(final_rel, path); } + else + { + /* + * Generate paths for the final_rel. Insert all surviving paths, with + * LockRows, Limit, and/or ModifyTable steps added if needed. + */ + foreach(lc, current_rel->pathlist) + { + Path *path = (Path *) lfirst(lc); + + /* + * If there is a FOR [KEY] UPDATE/SHARE clause, add the LockRows + * node. (Note: we intentionally test parse->rowMarks not + * root->rowMarks here. If there are only non-locking rowmarks, + * they should be handled by the ModifyTable node instead. + * However, root->rowMarks is what goes into the LockRows node.) + */ + if (parse->rowMarks) + { + path = (Path *) + create_lockrows_path(root, final_rel, path, + root->rowMarks, + SS_assign_special_param(root)); + } + + /* + * If there is a LIMIT/OFFSET clause, add the LIMIT node. + */ + if (limit_needed(parse)) + { + path = (Path *) create_limit_path(root, final_rel, path, + parse->limitOffset, + parse->limitCount, + offset_est, count_est); + } + + /* + * If this is an INSERT/UPDATE/DELETE, and we're not being called + * from inheritance_planner, add the ModifyTable node. + */ + if (parse->commandType != CMD_SELECT) + { + List *withCheckOptionLists; + List *returningLists; + List *rowMarks; + + /* + * Set up the WITH CHECK OPTION and RETURNING lists-of-lists, + * if needed. + */ + if (parse->withCheckOptions) + withCheckOptionLists = list_make1(parse->withCheckOptions); + else + withCheckOptionLists = NIL; + + if (parse->returningList) + returningLists = list_make1(parse->returningList); + else + returningLists = NIL; + + /* + * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows + * node will have dealt with fetching non-locked marked rows, + * else we need to have ModifyTable do that. + */ + if (parse->rowMarks) + rowMarks = NIL; + else + rowMarks = root->rowMarks; + + path = (Path *) + create_modifytable_path(root, final_rel, + parse->commandType, + parse->canSetTag, + parse->resultRelation, + NIL, + false, + list_make1_int(parse->resultRelation), + list_make1(path), + list_make1(root), + withCheckOptionLists, + returningLists, + rowMarks, + parse->onConflict, + SS_assign_special_param(root)); + } + + /* And shove it into final_rel */ + add_path(final_rel, path); + } + } /* * Generate partial paths for final_rel, too, if outer query levels might @@ -2259,6 +1818,233 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, } /* + * inheritance_adjust_scanjoin_target + * adjusts query's targetlist for each partition in the partition tree + * whose root is 'parent' and apply it to their paths via + * apply_scanjoin_target_to_paths + * + * Its output also consists of various pieces of information that will go + * into the ModifyTable node that will be created for this query. + */ +static void +inheritance_adjust_scanjoin_target(PlannerInfo *root, + Query *parse, + RelOptInfo *parent, + List **subroots, + Index *nominalRelation, + List **partitioned_rels, + List **resultRelations, + List **subpaths, + List **WCOLists, + List **returningLists, + List **rowMarks) +{ + ListCell *lc; + + /* + * If all of child relations of parent were pruned, let + * apply_scanjoin_target_to_paths finish it up with a dummy path + * containing correct target list. + */ + if (IS_DUMMY_REL(parent)) + { + PathTarget *scanjoin_target = create_pathtarget(root, + root->processed_tlist); + apply_scanjoin_target_to_paths(root, parent, + list_make1(scanjoin_target), + NIL, false, false); + return; + } + + if (parent->part_scheme) + { + *partitioned_rels = lappend(*partitioned_rels, + list_make1_int(parent->relid)); + if (*nominalRelation == 0) + *nominalRelation = parent->relid; + } + + foreach(lc, root->inh_target_child_rels) + { + RelOptInfo *child_joinrel = lfirst(lc); + RelOptInfo *child_rel; + AppendRelInfo *appinfo; + int child_base_relid; + bool found; + List *tlist; + PathTarget *scanjoin_target; + bool scanjoin_target_parallel_safe; + bool scanjoin_target_same_exprs; + PlannerInfo *partition_subroot; + Query *partition_parse; + + /* + * Check if this child joinrel is a child of parent. To do so, check + * if its relids contains an index that belongs to one of parent's + * child base rels. + */ + found = false; + child_base_relid = -1; + while ((child_base_relid = bms_next_member(child_joinrel->relids, + child_base_relid)) >= 0) + { + AppendRelInfo *appinfo = root->append_rel_array[child_base_relid]; + + if (appinfo && appinfo->parent_relid == parent->relid) + { + found = true; + break; + } + } + if (!found) + continue; + + if (*nominalRelation == 0) + *nominalRelation = child_base_relid; + + /* + * If the child was pruned, its corresponding joinrel will be empty, + * which we can ignore safely. + */ + if (IS_DUMMY_REL(child_joinrel)) + continue; + + appinfo = root->append_rel_array[child_base_relid]; + Assert(appinfo != NULL); + child_rel = root->simple_rel_array[child_base_relid]; + Assert(IS_SIMPLE_REL(child_rel)); + + /* Translate Query structure for this partition. */ + partition_parse = (Query *) + adjust_appendrel_attrs(root, + (Node *) parse, + 1, &appinfo); + + if (child_rel->part_scheme) + { + Assert(child_joinrel == child_rel); + inheritance_adjust_scanjoin_target(root, + partition_parse, + child_rel, + subroots, + nominalRelation, + partitioned_rels, + resultRelations, + subpaths, + WCOLists, + returningLists, + rowMarks); + /* Restore the Query for processing the next partition. */ + } + else + { + /* + * Generate a separate PlannerInfo for this partition. We'll need + * it when generating the ModifyTable subplan for this partition. + */ + partition_subroot = makeNode(PlannerInfo); + memcpy(partition_subroot, root, sizeof(PlannerInfo)); + *subroots = lappend(*subroots, partition_subroot); + + /* + * Preprocess the translated targetlist and save it in the + * partition's PlannerInfo for use by createplan.c. + */ + partition_subroot->parse = partition_parse; + tlist = preprocess_targetlist(partition_subroot); + partition_subroot->processed_tlist = tlist; + + /* + * Fix up child rel's reltarget to add Vars from the top-level + * targetlist that may not have yet been added to it. Note that + * it would already contain entries for inherited attributes + * (although we may still need to set their attr_needed), but + * not its own attributes that preprocess_targetlist called + * above would've added to the top-level tlist. + */ + adjust_child_reltarget(partition_subroot, bms_make_singleton(0), + tlist); + + /* + * Apply the newly computed scan/join target to child_joinrel's + * paths. + */ + scanjoin_target = create_pathtarget(root, tlist); + scanjoin_target_same_exprs = + equal(scanjoin_target->exprs, + child_joinrel->reltarget->exprs); + scanjoin_target_parallel_safe = + is_parallel_safe(root, (Node *) scanjoin_target->exprs); + apply_scanjoin_target_to_paths(root, child_joinrel, + list_make1(scanjoin_target), + NIL, + scanjoin_target_parallel_safe, + scanjoin_target_same_exprs); + + /* Collect information that will go into the ModifyTable */ + *resultRelations = lappend_int(*resultRelations, child_base_relid); + *subpaths = lappend(*subpaths, child_joinrel->cheapest_total_path); + if (partition_parse->withCheckOptions) + *WCOLists = lappend(*WCOLists, partition_parse->withCheckOptions); + if (partition_parse->returningList) + *returningLists = lappend(*returningLists, + partition_parse->returningList); + if (partition_parse->rowMarks) + *rowMarks = lappend(*rowMarks, partition_parse->rowMarks); + } + } +} + +static void +adjust_child_reltarget(PlannerInfo *root, Relids where_needed, List *tlist) +{ + List *tlist_vars = pull_var_clause((Node *) tlist, + PVC_RECURSE_AGGREGATES | + PVC_RECURSE_WINDOWFUNCS | + PVC_INCLUDE_PLACEHOLDERS); + ListCell *lc; + + foreach(lc, tlist_vars) + { + Node *node = (Node *) lfirst(lc); + + if (IsA(node, Var)) + { + Var *var = (Var *) node; + RelOptInfo *rel = find_base_rel(root, var->varno); + int attno = var->varattno; + + if (bms_is_subset(where_needed, rel->relids)) + continue; + + Assert(attno >= rel->min_attr && attno <= rel->max_attr); + attno -= rel->min_attr; + if (rel->attr_needed[attno] == NULL) + { + bool found; + ListCell *lc2; + + rel->attr_needed[attno] = + bms_add_members(rel->attr_needed[attno], + where_needed); + found = false; + foreach(lc2, rel->reltarget->exprs) + { + if (equal(var, lfirst(lc2))) + { + found = true; + break; + } + } + if (!found) + rel->reltarget->exprs = lappend(rel->reltarget->exprs, + var); + } + } + } +} + +/* * Do preprocessing for groupingSets clause and related data. This handles the * preliminary steps of expanding the grouping sets, organizing them into lists * of rollups, and preparing annotations which will later be filled in with diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index c3f46a26c3..0d10a6b914 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -914,7 +914,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->grouping_map = NULL; subroot->minmax_aggs = NIL; subroot->qual_security_level = 0; - subroot->inhTargetKind = INHKIND_NONE; subroot->hasRecursion = false; subroot->wt_param_id = -1; subroot->non_recursive_path = NULL; diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 690b6bbab7..f4c485cdc9 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -2265,8 +2265,34 @@ adjust_appendrel_attrs_mutator(Node *node, context->appinfos); return (Node *) phv; } + + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 8369e3ad62..5a37221780 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1265,36 +1265,6 @@ get_relation_constraints(PlannerInfo *root, } } - /* - * Append partition predicates, if any. - * - * For selects, partition pruning uses the parent table's partition bound - * descriptor, instead of constraint exclusion which is driven by the - * individual partition's partition constraint. - */ - if (enable_partition_pruning && root->parse->commandType != CMD_SELECT) - { - List *pcqual = RelationGetPartitionQual(relation); - - if (pcqual) - { - /* - * Run the partition quals through const-simplification similar to - * check constraints. We skip canonicalize_qual, though, because - * partition quals should be in canonical form already; also, - * since the qual is in implicit-AND format, we'd have to - * explicitly convert it to explicit-AND format and back again. - */ - pcqual = (List *) eval_const_expressions(root, (Node *) pcqual); - - /* Fix Vars to have the desired varno */ - if (varno != 1) - ChangeVarNodes((Node *) pcqual, 1, varno, 0); - - result = list_concat(result, pcqual); - } - } - heap_close(relation, NoLock); return result; @@ -1421,31 +1391,15 @@ relation_excluded_by_constraints(PlannerInfo *root, switch (constraint_exclusion) { case CONSTRAINT_EXCLUSION_OFF: - - /* - * Don't prune if feature turned off -- except if the relation is - * a partition. While partprune.c-style partition pruning is not - * yet in use for all cases (update/delete is not handled), it - * would be a UI horror to use different user-visible controls - * depending on such a volatile implementation detail. Therefore, - * for partitioned tables we use enable_partition_pruning to - * control this behavior. - */ - if (root->inhTargetKind == INHKIND_PARTITIONED) - break; return false; case CONSTRAINT_EXCLUSION_PARTITION: /* * When constraint_exclusion is set to 'partition' we only handle - * OTHER_MEMBER_RELs, or BASERELs in cases where the result target - * is an inheritance parent or a partitioned table. + * OTHER_MEMBER_RELs. */ - if ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) && - !(rel->reloptkind == RELOPT_BASEREL && - root->inhTargetKind != INHKIND_NONE && - rel->relid == root->parse->resultRelation)) + if (rel->reloptkind != RELOPT_OTHER_MEMBER_REL) return false; break; diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index adb4265047..c008d626a1 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -319,9 +319,6 @@ typedef struct PlannerInfo Index qual_security_level; /* minimum security_level for quals */ /* Note: qual_security_level is zero if there are no securityQuals */ - InheritanceKind inhTargetKind; /* indicates if the target relation is an - * inheritance child or partition or a - * partitioned table */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */ bool hasDeletedRTEs; /* true if any RTE was deleted from jointree */ @@ -343,6 +340,9 @@ typedef struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* RelOptInfo of child joinrels of an inherited update/delete operation */ + List *inh_target_child_rels; } PlannerInfo; diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index 3ba3aaf2d8..a539280851 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1781,7 +1781,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_1 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_1 -> Nested Loop Semi Join @@ -1789,7 +1789,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_2 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_2 (28 rows) -- 2.11.0
v3-0002-Lazy-creation-of-partition-objects-for-planning.patch (text/plain, 49.1K) download From 2abe3703b65caffd71068aa252b15b3d6a3c5da5 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 16 May 2018 14:35:40 +0900 Subject: [PATCH v3 2/4] Lazy creation of partition objects for planning With the current approach, *all* partitions are opened and range table entries are created for them in the planner's prep phase, which is much sooner than when partition pruning is performed. This means that query_planner ends up spending cycles and memory on many partitions that potentially won't be included in the plan, such as creating RelOptInfos, AppendRelInfos. To avoid that, add partition range table entries and other planning data structures for only partitions that remain after applying partition pruning. Some code like that of partitionwise join rely on the fact that even though partitions may have been pruned, they would still have a RelOptInfo, albeit marked dummy to handle the outer join case where the pruned partition appears on the nullable side of join. So this commit also teaches the partitionwise join code to allocate dummy RelOptInfos for pruned partitions. There are couple of regression test diffs caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child and also that the individual partition RT entries are now created in the order in which their parent's are processed whereas previously they'd be added to the range table in the order of depth-first expansion of the tree. --- src/backend/optimizer/path/allpaths.c | 61 +++-- src/backend/optimizer/path/joinrels.c | 8 + src/backend/optimizer/plan/initsplan.c | 66 +++++ src/backend/optimizer/plan/planmain.c | 30 --- src/backend/optimizer/plan/planner.c | 12 +- src/backend/optimizer/prep/prepunion.c | 314 +++++++++------------- src/backend/optimizer/util/plancat.c | 20 +- src/backend/optimizer/util/relnode.c | 172 ++++++++++-- src/backend/partitioning/partprune.c | 109 ++++---- src/include/nodes/relation.h | 5 + src/include/optimizer/pathnode.h | 6 + src/include/optimizer/plancat.h | 2 +- src/include/optimizer/planmain.h | 3 + src/include/optimizer/prep.h | 10 + src/include/partitioning/partprune.h | 2 +- src/test/regress/expected/join.out | 22 +- src/test/regress/expected/partition_aggregate.out | 4 +- 17 files changed, 507 insertions(+), 339 deletions(-) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 968cac42a3..e54cccd077 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -151,6 +151,7 @@ make_one_rel(PlannerInfo *root, List *joinlist) { RelOptInfo *rel; Index rti; + double total_pages; /* * Construct the all_baserels Relids set. @@ -181,6 +182,35 @@ make_one_rel(PlannerInfo *root, List *joinlist) * then generate access paths. */ set_base_rel_sizes(root); + + /* + * We should now have size estimates for every actual table involved in + * the query, and we also know which if any have been deleted from the + * query by join removal; so we can compute total_table_pages. + * + * Note that appendrels are not double-counted here, even though we don't + * bother to distinguish RelOptInfos for appendrel parents, because the + * parents will still have size zero. + * + * XXX if a table is self-joined, we will count it once per appearance, + * which perhaps is the wrong thing ... but that's not completely clear, + * and detecting self-joins here is difficult, so ignore it for now. + */ + total_pages = 0; + for (rti = 1; rti < root->simple_rel_array_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + + if (brel == NULL) + continue; + + Assert(brel->relid == rti); /* sanity check on array */ + + if (IS_SIMPLE_REL(brel)) + total_pages += (double) brel->pages; + } + root->total_table_pages = total_pages; + set_base_rel_pathlists(root); /* @@ -897,8 +927,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -914,21 +942,14 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, * partitioned table's list will contain all such indexes. */ if (rte->relkind == RELKIND_PARTITIONED_TABLE) + { rel->partitioned_child_rels = list_make1_int(rti); - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; + /* + * And do prunin. Note that this adds AppendRelInfo's of only the + * partitions that are not pruned. + */ + prune_append_rel_partitions(root, rel); } /* @@ -1117,13 +1138,10 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, /* * Mark the child as empty in certain cases, such as if a restriction - * clause reduced to constant FALSE or NULL, or if the partition - * of a partitioned table has been pruned, or if constaint exclusion + * clause reduced to constant FALSE or NULL, or if constaint exclusion * pruned it. */ if (have_const_false_cq || - (did_pruning && - !bms_is_member(appinfo->child_relid, live_children)) || relation_excluded_by_constraints(root, childrel, childRTE)) { /* @@ -1136,10 +1154,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, (Node *) rel->reltarget->exprs, 1, &appinfo); - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ set_dummy_rel_pathlist(childrel); continue; } @@ -2653,6 +2667,7 @@ inheritance_make_rel_from_joinlist(PlannerInfo *root, continue; childrel = find_base_rel(root, appinfo->child_relid); + Assert(childrel != NULL); /* Ignore excluded/pruned children. */ if (IS_DUMMY_REL(childrel)) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index d3d21fed5d..8543e633c2 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1376,6 +1376,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts); + if (child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts); + /* We should never try to join two overlapping sets of rels. */ Assert(!bms_overlap(child_rel1->relids, child_rel2->relids)); child_joinrelids = bms_union(child_rel1->relids, child_rel2->relids); @@ -1414,6 +1419,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } } diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 01335db511..beb3e95101 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -132,6 +132,72 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode) (int) nodeTag(jtnode)); } +/* + * add_rel_partitions_to_query + * create range table entries and "otherrel" RelOptInfos and for the + * partitions of 'rel' specified by the caller + * + * To store the objects thus created, various arrays in 'root' are expanded + * by repalloc'ing them. + */ +void +add_rel_partitions_to_query(PlannerInfo *root, RelOptInfo *rel, + bool scan_all_parts, + Bitmapset *partindexes) +{ + int new_size; + int num_added_parts; + int i; + + Assert(partindexes != NULL || scan_all_parts); + + /* Expand the PlannerInfo arrays to hold new partition objects. */ + num_added_parts = scan_all_parts ? rel->nparts : + bms_num_members(partindexes); + new_size = root->simple_rel_array_size + num_added_parts; + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + if (root->append_rel_array) + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + else + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + + /* Set the contents of just allocated memory to 0. */ + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * num_added_parts); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * num_added_parts); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * num_added_parts); + root->simple_rel_array_size = new_size; + + /* And add the partitions. */ + if (scan_all_parts) + { + for (i = 0; i < rel->nparts; i++) + { + rel->part_rels[i] = build_partition_rel(root, rel, + rel->part_oids[i]); + rel->live_parts = bms_add_member(rel->live_parts, i); + } + } + else + { + rel->live_parts = partindexes; + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) + rel->part_rels[i] = build_partition_rel(root, rel, + rel->part_oids[i]); + } +} /***************************************************************************** * diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 3f0d80eaa6..1bd3f0e350 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -57,8 +57,6 @@ query_planner(PlannerInfo *root, List *tlist, Query *parse = root->parse; List *joinlist; RelOptInfo *final_rel; - Index rti; - double total_pages; /* * If the query has an empty join tree, then it's something easy like @@ -232,34 +230,6 @@ query_planner(PlannerInfo *root, List *tlist, extract_restriction_or_clauses(root); /* - * We should now have size estimates for every actual table involved in - * the query, and we also know which if any have been deleted from the - * query by join removal; so we can compute total_table_pages. - * - * Note that appendrels are not double-counted here, even though we don't - * bother to distinguish RelOptInfos for appendrel parents, because the - * parents will still have size zero. - * - * XXX if a table is self-joined, we will count it once per appearance, - * which perhaps is the wrong thing ... but that's not completely clear, - * and detecting self-joins here is difficult, so ignore it for now. - */ - total_pages = 0; - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - - if (brel == NULL) - continue; - - Assert(brel->relid == rti); /* sanity check on array */ - - if (IS_SIMPLE_REL(brel)) - total_pages += (double) brel->pages; - } - root->total_table_pages = total_pages; - - /* * Ready to do the primary planning. */ final_rel = make_one_rel(root, joinlist); diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 9636d7a4ee..a757aba551 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6708,18 +6708,21 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, */ if (rel->part_scheme && rel->part_rels) { - int partition_idx; + int i; List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + i = -1; + while ((i = bms_next_member(rel->live_parts, i)) >= 0) { - RelOptInfo *child_rel = rel->part_rels[partition_idx]; + RelOptInfo *child_rel = rel->part_rels[i]; ListCell *lc; AppendRelInfo **appinfos; int nappinfos; List *child_scanjoin_targets = NIL; + Assert(child_rel != NULL); + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -6821,6 +6824,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index f4c485cdc9..279f686fb0 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -49,6 +49,8 @@ #include "parser/parse_coerce.h" #include "parser/parsetree.h" #include "utils/lsyscache.h" +#include "utils/lsyscache.h" +#include "utils/partcache.h" #include "utils/rel.h" #include "utils/selfuncs.h" #include "utils/syscache.h" @@ -101,21 +103,10 @@ static List *generate_append_tlist(List *colTypes, List *colCollations, static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist); static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti); -static void expand_partitioned_rtentry(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); -static void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + RangeTblEntry *oldrte, RangeTblEntry *newrte, + Index newvarno, List **translated_vars); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, @@ -1522,6 +1513,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) LOCKMODE lockmode; List *inhOIDs; ListCell *l; + List *appinfos = NIL; /* Does RT entry allow inheritance? */ if (!rte->inh) @@ -1585,173 +1577,58 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) if (oldrc) oldrc->isParent = true; + /* Partitioned tables are expanded elsewhere. */ + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + { + list_free(inhOIDs); + return; + } + /* * Must open the parent relation to examine its tupdesc. We need not lock * it; we assume the rewriter already did. */ oldrelation = heap_open(parentOID, NoLock); - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) + foreach(l, inhOIDs) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); + Oid childOID = lfirst_oid(l); + Index childRTindex = 0; + RangeTblEntry *childrte = NULL; + AppendRelInfo *appinfo = NULL; - /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. - */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); + add_inheritance_child_to_query(root, rte, rti, + oldrelation->rd_rel->reltype, + RelationGetDescr(oldrelation), + oldrc, childOID, NoLock, + &appinfo, &childrte, + &childRTindex); + Assert(childRTindex > 1); + Assert(childrte != NULL); + Assert(appinfo != NULL); + appinfos = lappend(appinfos, appinfo); } + + /* + * If all the children were temp tables, pretend it's a + * non-inheritance situation; we don't need Append node in that case. + * The duplicate RTE we added for the parent table is harmless, so we + * don't bother to get rid of it; ditto for the useless PlanRowMark + * node. + */ + if (list_length(appinfos) < 2) + rte->inh = false; else - { - List *appinfos = NIL; - RangeTblEntry *childrte; - Index childRTindex; - - /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. - */ - foreach(l, inhOIDs) - { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = heap_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - heap_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - heap_close(newrelation, NoLock); - } - - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); - - } + root->append_rel_list = list_concat(root->append_rel_list, + appinfos); heap_close(oldrelation, NoLock); } /* - * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. - * - * Note that RelationGetPartitionDispatchInfo will expand partitions in the - * same order as this code. - */ -static void -expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) -{ - int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); - - check_stack_depth(); - - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); - - /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. - */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); - - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); - - /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. - */ - if (partdesc->nparts == 0) - { - parentrte->inh = false; - return; - } - - for (i = 0; i < partdesc->nparts; i++) - { - Oid childOID = partdesc->oids[i]; - Relation childrel; - - /* Open rel; we already have required locks */ - childrel = heap_open(childOID, NoLock); - - /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. - */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); - - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); - - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); - - /* Close child relation, but keep locks */ - heap_close(childrel, NoLock); - } -} - -/* - * expand_single_inheritance_child + * add_inheritance_child_to_query * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. + * maybe a PlanRowMark for a child relation. * * We now expand the partition hierarchy level by level, creating a * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each @@ -1769,19 +1646,70 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, * The child RangeTblEntry and its RTI are returned in "childrte_p" and * "childRTindex_p" resp. */ -static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +void +add_inheritance_child_to_query(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, Oid parentRelType, + TupleDesc parentDesc, + PlanRowMark *top_parentrc, + Oid childOID, int lockmode, + AppendRelInfo **appinfo_p, + RangeTblEntry **childrte_p, + Index *childRTindex_p) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); - Oid childOID = RelationGetRelid(childrel); + Oid parentOID = parentrte->relid; RangeTblEntry *childrte; Index childRTindex; AppendRelInfo *appinfo; + Relation childrel = NULL; + char child_relkind; + Oid child_reltype; + TupleDesc childDesc; + + *appinfo_p = NULL; + *childrte_p = NULL; + *childRTindex_p = 0; + + /* Open rel if needed; we already have required locks */ + if (childOID != parentOID) + { + childrel = heap_open(childOID, lockmode); + + /* + * Temporary partitions belonging to other sessions should have been + * disallowed at definition, but for paranoia's sake, let's double + * check. + */ + if (RELATION_IS_OTHER_TEMP(childrel)) + { + if (childrel->rd_rel->relispartition) + elog(ERROR, "temporary relation from another session found as partition"); + heap_close(childrel, lockmode); + return; + } + + child_relkind = childrel->rd_rel->relkind; + + /* + * No point in adding to the query a partitioned table that has no + * partitions. + */ + if (child_relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(childrel)->nparts == 0) + { + heap_close(childrel, lockmode); + return; + } + + child_reltype = childrel->rd_rel->reltype; + childDesc = RelationGetDescr(childrel); + } + else + { + child_relkind = parentrte->relkind; + child_reltype = parentRelType; + childDesc = parentDesc; + } /* * Build an RTE for the child, and attach to query's rangetable list. We @@ -1798,7 +1726,7 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, childrte = copyObject(parentrte); *childrte_p = childrte; childrte->relid = childOID; - childrte->relkind = childrel->rd_rel->relkind; + childrte->relkind = child_relkind; /* A partitioned child will need to be expanded further. */ if (childOID != parentOID && childrte->relkind == RELKIND_PARTITIONED_TABLE) @@ -1823,12 +1751,13 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, appinfo = makeNode(AppendRelInfo); appinfo->parent_relid = parentRTindex; appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->rd_rel->reltype; - appinfo->child_reltype = childrel->rd_rel->reltype; - make_inh_translation_list(parentrel, childrel, childRTindex, + appinfo->parent_reltype = parentRelType; + appinfo->child_reltype = child_reltype; + make_inh_translation_list(parentDesc, childDesc, + parentrte, childrte, childRTindex, &appinfo->translated_vars); appinfo->parent_reloid = parentOID; - *appinfos = lappend(*appinfos, appinfo); + *appinfo_p = appinfo; /* * Translate the column permissions bitmaps to the child's attnums (we @@ -1879,6 +1808,13 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } + + /* Close child relations, but keep locks */ + if (childOID != parentOID) + { + Assert(childrel != NULL); + heap_close(childrel, lockmode); + } } /* @@ -1889,14 +1825,12 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, * For paranoia's sake, we match type/collation as well as attribute name. */ static void -make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, - List **translated_vars) +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + RangeTblEntry *oldrte, RangeTblEntry *newrte, + Index newvarno, List **translated_vars) { List *vars = NIL; - TupleDesc old_tupdesc = RelationGetDescr(oldrelation); - TupleDesc new_tupdesc = RelationGetDescr(newrelation); - Oid new_relid = RelationGetRelid(newrelation); + Oid new_relid = newrte->relid; int oldnatts = old_tupdesc->natts; int newnatts = new_tupdesc->natts; int old_attno; @@ -1926,7 +1860,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, * When we are generating the "translation list" for the parent table * of an inheritance set, no need to search for matches. */ - if (oldrelation == newrelation) + if (oldrte->relid == newrte->relid) { vars = lappend(vars, makeVar(newvarno, (AttrNumber) (old_attno + 1), @@ -1955,7 +1889,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, newtup = SearchSysCacheAttName(new_relid, attname); if (!newtup) elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(newrte->relid)); new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; ReleaseSysCache(newtup); @@ -1965,10 +1899,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, /* Found it, check type and collation match */ if (atttypid != att->atttypid || atttypmod != att->atttypmod) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(newrte->relid)); if (attcollation != att->attcollation) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(newrte->relid)); vars = lappend(vars, makeVar(newvarno, (AttrNumber) (new_attno + 1), @@ -2121,7 +2055,7 @@ adjust_appendrel_attrs_mutator(Node *node, } } - if (var->varlevelsup == 0 && appinfo) + if (var->varlevelsup == 0 && appinfo && appinfo->translated_vars) { var->varno = appinfo->child_relid; var->varnoold = appinfo->child_relid; diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 5a37221780..77ae4b538a 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -106,7 +106,7 @@ static void set_baserel_partition_key_exprs(Relation relation, */ void get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) + Bitmapset *updatedCols, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; @@ -449,7 +449,15 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * inheritance parents may be partitioned. */ if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { set_relation_partition_info(root, rel, relation); + if (!root->partColsUpdated) + root->partColsUpdated = + has_partition_attrs(relation, updatedCols, NULL); + } + + rel->tupdesc = RelationGetDescr(relation); + rel->reltype = RelationGetForm(relation)->reltype; heap_close(relation, NoLock); @@ -1855,16 +1863,20 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; + + /* + * Since we must've taken a lock on the table, it's okay to simply copy + * the pointers to relcache data here. + */ + rel->part_oids = partdesc->oids; + rel->boundinfo = partdesc->boundinfo; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 39f5729b91..d73b4a8499 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -16,6 +16,7 @@ #include <limits.h> +#include "catalog/pg_class.h" #include "miscadmin.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" @@ -27,6 +28,7 @@ #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" #include "partitioning/partbounds.h" +#include "storage/lockdefs.h" #include "utils/hsearch.h" @@ -142,6 +144,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) /* Rel should not exist already */ Assert(relid > 0 && relid < root->simple_rel_array_size); + if (root->simple_rel_array[relid] != NULL) elog(ERROR, "rel %d already exists", relid); @@ -224,7 +227,8 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte->relid, rte->inh, rte->updatedCols, + rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -274,41 +278,30 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) if (rte->inh) { ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - if (nparts > 0) + /* + * For partitioned tables, we just allocate space for RelOptInfo's. + * pointers for all partitions and copy the partition OIDs from the + * relcache. Actual RelOptInfo is built for a partition only if it is + * not pruned. + */ + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + { rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); + palloc0(sizeof(RelOptInfo *) * rel->nparts); + return rel; + } foreach(l, root->append_rel_list) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != relid) continue; - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; + (void) build_simple_rel(root, appinfo->child_relid, rel); } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); } return rel; @@ -1747,6 +1740,137 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; +} + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx) +{ + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (noop) AppendRelInfo for parent, because we're setting + * the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + AppendRelInfo *appinfo = makeNode(AppendRelInfo); + + appinfo->parent_relid = parent->relid; + appinfo->child_relid = parent->relid; + appinfo->parent_reltype = parent->reltype; + appinfo->child_reltype = parent->reltype; + /* leaving translated_vars to NIL to mean no translation needed */ + appinfo->parent_reloid = root->simple_rte_array[parent->relid]->relid; + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} + +/* + * build_partition_rel + * This adds a valid partition to the query by adding it to the + * range table and creating planner data structures for it + */ +RelOptInfo * +build_partition_rel(PlannerInfo *root, RelOptInfo *parent, Oid partoid) +{ + RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; + RelOptInfo *result; + Index partRTindex = 0; + RangeTblEntry *partrte = NULL; + AppendRelInfo *appinfo = NULL; + PlanRowMark *rootrc = NULL; + + /* Locate the root partitioned table and fetch its PlanRowMark, if any. */ + if (root->rowMarks) + { + Index rootRTindex = 0; + + /* + * The root partitioned table itself might be a child of UNION ALL + * parent, so we must resort to finding the root parent like this. + */ + rootRTindex = parent->relid; + if (root->append_rel_array[rootRTindex]) + { + AppendRelInfo *tmp = root->append_rel_array[rootRTindex]; + + /* + * Keep moving up until we each the parent rel that's not a + * partitioned table. The one before that one would be the root + * parent. + */ + while(root->simple_rel_array[rootRTindex]->part_scheme) + { + tmp = root->append_rel_array[tmp->parent_relid]; + if (tmp == NULL) + break; + rootRTindex = tmp->parent_relid; + } + } + + rootrc = get_plan_rowmark(root->rowMarks, rootRTindex); + } + + /* + * expand_inherited_rtentry alreay locked all partitions, so pass + * NoLock for lockmode. + */ + add_inheritance_child_to_query(root, parentrte, parent->relid, + parent->reltype, parent->tupdesc, + rootrc, partoid, NoLock, + &appinfo, &partrte, &partRTindex); + + /* Partition turned out to be a partitioned table with 0 partitions. */ + if (partrte == NULL) + return NULL; + + Assert(appinfo != NULL); + root->append_rel_list = lappend(root->append_rel_list, appinfo); + root->simple_rte_array[partRTindex] = partrte; + root->append_rel_array[partRTindex] = appinfo; + + /* Build the RelOptInfo. */ + result = build_simple_rel(root, partRTindex, parent); + + /* Set the information created by create_lateral_join_info(). */ + result->direct_lateral_relids = parent->direct_lateral_relids; + result->lateral_relids = parent->lateral_relids; + result->lateral_referencers = parent->lateral_referencers; + + return result; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index e1ce8b4ddc..345ab19989 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -45,7 +45,9 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/pathnode.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/predtest.h" #include "optimizer/prep.h" @@ -437,26 +439,26 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); - present_parts = NULL; + memset(subpart_map, -1, nparts * sizeof(int)); + Assert(IS_SIMPLE_REL(subpart)); + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; + subplanidx = relid_subplan_map[partrel->relid] - 1; + subpartidx = relid_subpart_map[partrel->relid] - 1; subplan_map[i] = subplanidx; subpart_map[i] = subpartidx; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } rte = root->simple_rte_array[subpart->relid]; @@ -548,61 +550,68 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) * * Callers must ensure that 'rel' is a partitioned table. */ -Relids -prune_append_rel_partitions(RelOptInfo *rel) +void +prune_append_rel_partitions(PlannerInfo *root, RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; - bool contradictory; + bool contradictory, + scan_all_parts = false; PartitionPruneContext context; - Bitmapset *partindexes; - int i; + Bitmapset *partindexes = NULL; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ if (rel->nparts == 0) - return NULL; + return; - /* - * Process clauses. If the clauses are found to be contradictory, we can - * return the empty set. - */ - pruning_steps = gen_partprune_steps(rel, clauses, &contradictory); - if (contradictory) - return NULL; - - /* Set up PartitionPruneContext */ - context.strategy = rel->part_scheme->strategy; - context.partnatts = rel->part_scheme->partnatts; - context.nparts = rel->nparts; - context.boundinfo = rel->boundinfo; - context.partcollation = rel->part_scheme->partcollation; - context.partsupfunc = rel->part_scheme->partsupfunc; - context.stepcmpfuncs = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * + if (enable_partition_pruning && clauses != NIL) + { + /* + * Process clauses. If the clauses are found to be contradictory, we + * can return the empty set. + */ + pruning_steps = gen_partprune_steps(rel, clauses, &contradictory); + if (!contradictory) + { + context.strategy = rel->part_scheme->strategy; + context.partnatts = rel->part_scheme->partnatts; + context.nparts = rel->nparts; + context.boundinfo = rel->boundinfo; + context.partcollation = rel->part_scheme->partcollation; + context.partsupfunc = rel->part_scheme->partsupfunc; + context.stepcmpfuncs = (FmgrInfo *) + palloc0(sizeof(FmgrInfo) * context.partnatts * list_length(pruning_steps)); - context.ppccontext = CurrentMemoryContext; + context.ppccontext = CurrentMemoryContext; - /* These are not valid when being called from the planner */ - context.partrel = NULL; - context.planstate = NULL; - context.exprstates = NULL; - context.exprhasexecparam = NULL; - context.evalexecparams = false; + /* These are not valid when being called from the planner */ + context.partrel = NULL; + context.planstate = NULL; + context.exprstates = NULL; + context.exprhasexecparam = NULL; + context.evalexecparams = false; - /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); + /* Actual pruning happens here. */ + partindexes = get_matching_partitions(&context, pruning_steps); - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); + /* No need to add partitions if all were pruned. */ + if (bms_is_empty(partindexes)) + return; + } + else + scan_all_parts = true; + } + else + scan_all_parts = true; - return result; + /* + * Build selected partitions' range table entries, RelOptInfos, and + * AppendRelInfos. + */ + add_rel_partitions_to_query(root, rel, scan_all_parts, partindexes); } /* diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index c008d626a1..051796e1f7 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -15,6 +15,7 @@ #define RELATION_H #include "access/sdir.h" +#include "access/tupdesc.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/params.h" @@ -699,11 +700,15 @@ typedef struct RelOptInfo int nparts; /* number of partitions */ struct PartitionBoundInfoData *boundinfo; /* Partition bounds */ List *partition_qual; /* partition constraint */ + Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* unpruned parts; NULL if all are live */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ + TupleDesc tupdesc; + Oid reltype; } RelOptInfo; /* diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 7c5ff22650..4f567765a4 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -297,5 +297,11 @@ extern RelOptInfo *build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel, RelOptInfo *inner_rel, RelOptInfo *parent_joinrel, List *restrictlist, SpecialJoinInfo *sjinfo, JoinType jointype); +extern RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + int partidx); +extern RelOptInfo *build_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + Oid partoid); #endif /* PATHNODE_H */ diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index 7d53cbbb87..edaf2a3b4f 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -26,7 +26,7 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); + bool inhparent, Bitmapset *updatedCols, RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index c8ab0280d2..1916a33467 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -73,6 +73,9 @@ extern int from_collapse_limit; extern int join_collapse_limit; extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode); +extern void add_rel_partitions_to_query(PlannerInfo *root, RelOptInfo *rel, + bool scan_all_parts, + Bitmapset *partindexes); extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist); extern void add_vars_to_targetlist(PlannerInfo *root, List *vars, Relids where_needed, bool create_new_ph); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 38608770a2..ca66f75544 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -49,6 +49,16 @@ extern RelOptInfo *plan_set_operations(PlannerInfo *root); extern void expand_inherited_tables(PlannerInfo *root); +extern void add_inheritance_child_to_query(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, Oid parentRelType, + TupleDesc parentDesc, + PlanRowMark *top_parentrc, + Oid childOID, int lockmode, + AppendRelInfo **appinfo_p, + RangeTblEntry **childrte_p, + Index *childRTindex_p); + extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h index b95c346bab..55a324583b 100644 --- a/src/include/partitioning/partprune.h +++ b/src/include/partitioning/partprune.h @@ -79,7 +79,7 @@ extern PartitionPruneInfo *make_partition_pruneinfo(PlannerInfo *root, List *subpaths, List *partitioned_rels, List *prunequal); -extern Relids prune_append_rel_partitions(RelOptInfo *rel); +extern void prune_append_rel_partitions(PlannerInfo *root, RelOptInfo *rel); extern Bitmapset *get_matching_partitions(PartitionPruneContext *context, List *pruning_steps); diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index dc6262be43..5f931591a6 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -5533,29 +5533,29 @@ select t1.b, ss.phv from join_ut1 t1 left join lateral (select t2.a as t2a, t3.a t3a, least(t1.a, t2.a, t3.a) phv from join_pt1 t2 join join_ut1 t3 on t2.a = t3.b) ss on t1.a = ss.t2a order by t1.a; - QUERY PLAN ------------------------------------------------------------------- + QUERY PLAN +-------------------------------------------------------------------- Sort - Output: t1.b, (LEAST(t1.a, t2.a, t3.a)), t1.a + Output: t1.b, (LEAST(t1.a, t2_1.a, t3.a)), t1.a Sort Key: t1.a -> Nested Loop Left Join - Output: t1.b, (LEAST(t1.a, t2.a, t3.a)), t1.a + Output: t1.b, (LEAST(t1.a, t2_1.a, t3.a)), t1.a -> Seq Scan on public.join_ut1 t1 Output: t1.a, t1.b, t1.c -> Hash Join - Output: t2.a, LEAST(t1.a, t2.a, t3.a) - Hash Cond: (t3.b = t2.a) + Output: t2_1.a, LEAST(t1.a, t2_1.a, t3.a) + Hash Cond: (t3.b = t2_1.a) -> Seq Scan on public.join_ut1 t3 Output: t3.a, t3.b, t3.c -> Hash - Output: t2.a + Output: t2_1.a -> Append - -> Seq Scan on public.join_pt1p1p1 t2 - Output: t2.a - Filter: (t1.a = t2.a) - -> Seq Scan on public.join_pt1p2 t2_1 + -> Seq Scan on public.join_pt1p1p1 t2_1 Output: t2_1.a Filter: (t1.a = t2_1.a) + -> Seq Scan on public.join_pt1p2 t2 + Output: t2.a + Filter: (t1.a = t2.a) (21 rows) select t1.b, ss.phv from join_ut1 t1 left join lateral diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 6bc106831e..1450cef057 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) -- 2.11.0
v3-0003-Only-lock-partitions-that-will-be-scanned-by-a-qu.patch (text/plain, 10.5K) download From 4255d72da27982934b27da9ae5268ba1d553c2d8 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Thu, 23 Aug 2018 17:30:18 +0900 Subject: [PATCH v3 3/4] Only lock partitions that will be scanned by a query --- src/backend/optimizer/prep/preptlist.c | 127 ++++++++++++++++++--------------- src/backend/optimizer/prep/prepunion.c | 15 ++-- src/backend/optimizer/util/relnode.c | 36 +++++++--- src/include/optimizer/prep.h | 6 +- 4 files changed, 111 insertions(+), 73 deletions(-) diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 8603feef2b..3f93e5933e 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -76,7 +76,6 @@ preprocess_targetlist(PlannerInfo *root) RangeTblEntry *target_rte = NULL; Relation target_relation = NULL; List *tlist; - ListCell *lc; /* * If there is a result relation, open it so we can look for missing @@ -118,12 +117,78 @@ preprocess_targetlist(PlannerInfo *root) tlist = expand_targetlist(tlist, command_type, result_relation, target_relation); + + tlist = add_rowmark_columns(root, tlist, root->rowMarks); + /* - * Add necessary junk columns for rowmarked rels. These values are needed - * for locking of rels selected FOR UPDATE/SHARE, and to do EvalPlanQual - * rechecking. See comments for PlanRowMark in plannodes.h. + * If the query has a RETURNING list, add resjunk entries for any Vars + * used in RETURNING that belong to other relations. We need to do this + * to make these Vars available for the RETURNING calculation. Vars that + * belong to the result rel don't need to be added, because they will be + * made to refer to the actual heap tuple. */ - foreach(lc, root->rowMarks) + if (parse->returningList && list_length(parse->rtable) > 1) + { + List *vars; + ListCell *l; + + vars = pull_var_clause((Node *) parse->returningList, + PVC_RECURSE_AGGREGATES | + PVC_RECURSE_WINDOWFUNCS | + PVC_INCLUDE_PLACEHOLDERS); + foreach(l, vars) + { + Var *var = (Var *) lfirst(l); + TargetEntry *tle; + + if (IsA(var, Var) && + var->varno == result_relation) + continue; /* don't need it */ + + if (tlist_member((Expr *) var, tlist)) + continue; /* already got it */ + + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + NULL, + true); + + tlist = lappend(tlist, tle); + } + list_free(vars); + } + + /* + * If there's an ON CONFLICT UPDATE clause, preprocess its targetlist too + * while we have the relation open. + */ + if (parse->onConflict) + parse->onConflict->onConflictSet = + expand_targetlist(parse->onConflict->onConflictSet, + CMD_UPDATE, + result_relation, + target_relation); + + if (target_relation) + heap_close(target_relation, NoLock); + + return tlist; +} + +/* + * add_rowmark_columns + * + * Add necessary junk columns for rowmarked rels. These values are needed + * for locking of rels selected FOR UPDATE/SHARE, and to do EvalPlanQual + * rechecking. See comments for PlanRowMark in plannodes.h. + */ +List * +add_rowmark_columns(PlannerInfo *root, List *tlist, List *rowMarks) +{ + List *range_table = root->parse->rtable; + ListCell *lc; + + foreach(lc, rowMarks) { PlanRowMark *rc = (PlanRowMark *) lfirst(lc); Var *var; @@ -183,58 +248,6 @@ preprocess_targetlist(PlannerInfo *root) } } - /* - * If the query has a RETURNING list, add resjunk entries for any Vars - * used in RETURNING that belong to other relations. We need to do this - * to make these Vars available for the RETURNING calculation. Vars that - * belong to the result rel don't need to be added, because they will be - * made to refer to the actual heap tuple. - */ - if (parse->returningList && list_length(parse->rtable) > 1) - { - List *vars; - ListCell *l; - - vars = pull_var_clause((Node *) parse->returningList, - PVC_RECURSE_AGGREGATES | - PVC_RECURSE_WINDOWFUNCS | - PVC_INCLUDE_PLACEHOLDERS); - foreach(l, vars) - { - Var *var = (Var *) lfirst(l); - TargetEntry *tle; - - if (IsA(var, Var) && - var->varno == result_relation) - continue; /* don't need it */ - - if (tlist_member((Expr *) var, tlist)) - continue; /* already got it */ - - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - NULL, - true); - - tlist = lappend(tlist, tle); - } - list_free(vars); - } - - /* - * If there's an ON CONFLICT UPDATE clause, preprocess its targetlist too - * while we have the relation open. - */ - if (parse->onConflict) - parse->onConflict->onConflictSet = - expand_targetlist(parse->onConflict->onConflictSet, - CMD_UPDATE, - result_relation, - target_relation); - - if (target_relation) - heap_close(target_relation, NoLock); - return tlist; } diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 279f686fb0..4d9583e63b 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -1555,14 +1555,15 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) lockmode = AccessShareLock; /* Scan for all members of inheritance set, acquire needed locks */ - inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); + if (rte->relkind != RELKIND_PARTITIONED_TABLE) + inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); /* * Check that there's at least one descendant, else treat as no-child * case. This could happen despite above has_subclass() check, if table * once had a child but no longer does. */ - if (list_length(inhOIDs) < 2) + if (rte->relkind != RELKIND_PARTITIONED_TABLE && list_length(inhOIDs) < 2) { /* Clear flag before returning */ rte->inh = false; @@ -1579,10 +1580,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) /* Partitioned tables are expanded elsewhere. */ if (rte->relkind == RELKIND_PARTITIONED_TABLE) - { - list_free(inhOIDs); return; - } /* * Must open the parent relation to examine its tupdesc. We need not lock @@ -1602,7 +1600,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) RelationGetDescr(oldrelation), oldrc, childOID, NoLock, &appinfo, &childrte, - &childRTindex); + &childRTindex, NULL); Assert(childRTindex > 1); Assert(childrte != NULL); Assert(appinfo != NULL); @@ -1654,7 +1652,8 @@ add_inheritance_child_to_query(PlannerInfo *root, RangeTblEntry *parentrte, Oid childOID, int lockmode, AppendRelInfo **appinfo_p, RangeTblEntry **childrte_p, - Index *childRTindex_p) + Index *childRTindex_p, + PlanRowMark **child_rowmark) { Query *parse = root->parse; Oid parentOID = parentrte->relid; @@ -1807,6 +1806,8 @@ add_inheritance_child_to_query(PlannerInfo *root, RangeTblEntry *parentrte, top_parentrc->allMarkTypes |= childrc->allMarkTypes; root->rowMarks = lappend(root->rowMarks, childrc); + if (child_rowmark) + *child_rowmark = childrc; } /* Close child relations, but keep locks */ diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index d73b4a8499..e98b626395 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1808,16 +1808,18 @@ build_partition_rel(PlannerInfo *root, RelOptInfo *parent, Oid partoid) { RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; RelOptInfo *result; + Index rootRTindex = 0; Index partRTindex = 0; RangeTblEntry *partrte = NULL; AppendRelInfo *appinfo = NULL; PlanRowMark *rootrc = NULL; + int orig_allMarkTypes; + PlanRowMark *childrc = NULL; + int lockmode; /* Locate the root partitioned table and fetch its PlanRowMark, if any. */ if (root->rowMarks) { - Index rootRTindex = 0; - /* * The root partitioned table itself might be a child of UNION ALL * parent, so we must resort to finding the root parent like this. @@ -1842,16 +1844,27 @@ build_partition_rel(PlannerInfo *root, RelOptInfo *parent, Oid partoid) } rootrc = get_plan_rowmark(root->rowMarks, rootRTindex); + + /* + * Addition of certain child rowmarks will change the value, in which + * case, we need to add additional columns to the query's targetlist. + */ + if (rootrc) + orig_allMarkTypes = rootrc->allMarkTypes; } - /* - * expand_inherited_rtentry alreay locked all partitions, so pass - * NoLock for lockmode. - */ + /* Determine the correct lockmode to use. */ + if (rootRTindex == root->parse->resultRelation) + lockmode = RowExclusiveLock; + else if (rootrc && RowMarkRequiresRowShareLock(rootrc->markType)) + lockmode = RowShareLock; + else + lockmode = AccessShareLock; add_inheritance_child_to_query(root, parentrte, parent->relid, parent->reltype, parent->tupdesc, - rootrc, partoid, NoLock, - &appinfo, &partrte, &partRTindex); + rootrc, partoid, lockmode, + &appinfo, &partrte, &partRTindex, + &childrc); /* Partition turned out to be a partitioned table with 0 partitions. */ if (partrte == NULL) @@ -1870,6 +1883,13 @@ build_partition_rel(PlannerInfo *root, RelOptInfo *parent, Oid partoid) result->lateral_relids = parent->lateral_relids; result->lateral_referencers = parent->lateral_referencers; + if (rootrc && rootrc->allMarkTypes != orig_allMarkTypes) + { + List *tlist = root->parse->targetList; + + tlist = add_rowmark_columns(root, tlist, list_make1(rootrc)); + } + return result; } diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index ca66f75544..ee7406e4f6 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -39,6 +39,9 @@ extern Expr *canonicalize_qual(Expr *qual, bool is_check); * prototypes for preptlist.c */ extern List *preprocess_targetlist(PlannerInfo *root); +extern List *add_rowmark_columns(PlannerInfo *root, + List *tlist, + List *rowMarks); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); @@ -57,7 +60,8 @@ extern void add_inheritance_child_to_query(PlannerInfo *root, Oid childOID, int lockmode, AppendRelInfo **appinfo_p, RangeTblEntry **childrte_p, - Index *childRTindex_p); + Index *childRTindex_p, + PlanRowMark **child_rowmark); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); -- 2.11.0
v3-0004-WIP-generate-all-inheritance-child-RelOptInfos-af.patch (text/plain, 63.7K) download From 45334d7f43033adbc9682d0cbb549301d2c4366d Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Thu, 13 Sep 2018 15:11:46 +0900 Subject: [PATCH v3 4/4] WIP: generate *all* inheritance child RelOptInfos after pruning --- src/backend/optimizer/path/allpaths.c | 209 ++++++--------- src/backend/optimizer/plan/initsplan.c | 57 ++-- src/backend/optimizer/plan/planner.c | 31 ++- src/backend/optimizer/prep/prepunion.c | 477 ++++++++++++++++++--------------- src/backend/optimizer/util/plancat.c | 58 ++-- src/backend/optimizer/util/relnode.c | 292 +++++++++++++------- src/backend/partitioning/partprune.c | 5 +- src/include/optimizer/pathnode.h | 9 +- src/include/optimizer/plancat.h | 9 +- src/include/optimizer/prep.h | 28 +- 10 files changed, 620 insertions(+), 555 deletions(-) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index e54cccd077..21723e7cdc 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -367,8 +367,23 @@ static void set_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { + + /* + * Check if the relation's constraints contradict the clauses matched + * to it. Only plain relations have constraints. In a partitioning + * hierarchy, but not with regular table inheritance, it's OK to assume + * that any constraints that hold for the parent also hold for every + * child; for instance, table inheritance allows the parent to have + * constraints marked NO INHERIT, but table partitioning does not. We + * choose to check whether the partitioning parents can be excluded here; + * doing so consumes some cycles, but potentially saves us the work of + * excluding each child individually. + */ if (rel->reloptkind == RELOPT_BASEREL && - relation_excluded_by_constraints(root, rel, rte)) + rte->rtekind == RTE_RELATION && + (!rte->inh || rte->relkind == RELKIND_PARTITIONED_TABLE) && + relation_excluded_by_constraints(root, rel->baserestrictinfo, + rti, rte->relid, false)) { /* * We proved we don't need to scan the rel via constraint exclusion, @@ -916,11 +931,7 @@ static void set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { - Relids top_parent_relids = rel->top_parent_relids; int parentRTindex = rti; - int rootParentRTindex = top_parent_relids ? - bms_singleton_member(top_parent_relids) : - parentRTindex; bool has_live_children; double parent_rows; double parent_size; @@ -951,6 +962,17 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, */ prune_append_rel_partitions(root, rel); } + else + { + expand_inherited_rtentry(root, rel, rte, rti); + if (IS_DUMMY_REL(rel)) + return; + if (!rte->inh) + { + set_rel_size(root, rel, rti, rte); + return; + } + } /* * If this is a partitioned baserel, set the consider_partitionwise_join @@ -989,9 +1011,9 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; + List *childquals = NIL; + bool have_const_false_cq = false; Index cq_min_security; - bool have_const_false_cq; ListCell *parentvars; ListCell *childvars; ListCell *lc; @@ -1004,53 +1026,23 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childRTE = root->simple_rte_array[childRTindex]; /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo may not have been created in some + * cases, for example, if it is not a partition. */ - childrel = find_base_rel(root, childRTindex); + childrel = root->simple_rel_array[childRTindex]; + if (childrel == NULL) + childrel = build_inheritance_child_rel(root, childRTindex, rel); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - /* - * We have to make child entries in the EquivalenceClass data - * structures as well. This is needed either if the parent - * participates in some eclass joins (because we will want to consider - * inner-indexscan joins on the individual children) or if the parent - * has useful pathkeys (because we should try to build MergeAppend - * paths that produce those sort orderings). Even if this child is - * deemed dummy, it may fall on nullable side in a child-join, which - * in turn may participate in a MergeAppend, where we will need the - * EquivalenceClass data structures. - */ - if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) - add_child_rel_equivalences(root, appinfo, rel, childrel); - childrel->has_eclass_joins = rel->has_eclass_joins; - - /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; + /* Convert parent's baserestrictinfo to store into childrel. */ foreach(lc, rel->baserestrictinfo) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; + Node *childqual = (Node *) rinfo->clause; ListCell *lc2; Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); + childqual = adjust_appendrel_attrs(root, childqual, 1, &appinfo); childqual = eval_const_expressions(root, childqual); /* check for flat-out constant */ if (childqual && IsA(childqual, Const)) @@ -1082,13 +1074,12 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, } /* reconstitute RestrictInfo with appropriate properties */ childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); cq_min_security = Min(cq_min_security, rinfo->security_level); } } @@ -1106,6 +1097,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, */ if (childRTE->securityQuals) { + List *security_quals = NIL; Index security_level = 0; foreach(lc, childRTE->securityQuals) @@ -1118,16 +1110,37 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Expr *qual = (Expr *) lfirst(lc2); /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); + security_quals = lappend(security_quals, + make_restrictinfo(qual, + true, false, + false, + security_level, + NULL, NULL, + NULL)); cq_min_security = Min(cq_min_security, security_level); } security_level++; } Assert(security_level <= root->qual_security_level); + + /* Concatenate with parent's quals. */ + childquals = list_concat(childquals, security_quals); + } + + /* + * Check if any of the security quals contradict table + * constraints. + */ + if (have_const_false_cq || + (childRTE->rtekind == RTE_RELATION && + relation_excluded_by_constraints(root, childquals, + childRTindex, + childRTE->relid, + true))) + { + childrel->reltarget = copy_pathtarget(rel->reltarget); + set_dummy_rel_pathlist(childrel); + continue; } /* @@ -1137,77 +1150,19 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childrel->baserestrict_min_security = cq_min_security; /* - * Mark the child as empty in certain cases, such as if a restriction - * clause reduced to constant FALSE or NULL, or if constaint exclusion - * pruned it. + * We have to make child entries in the EquivalenceClass data + * structures as well. This is needed either if the parent + * participates in some eclass joins (because we will want to consider + * inner-indexscan joins on the individual children) or if the parent + * has useful pathkeys (because we should try to build MergeAppend + * paths that produce those sort orderings). Even if this child is + * deemed dummy, it may fall on nullable side in a child-join, which + * in turn may participate in a MergeAppend, where we will need the + * EquivalenceClass data structures. */ - if (have_const_false_cq || - relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* - * The child relation won't be scanned, but certain partitionwise - * planning operations require its reltarget to contain valid - * expressions, so oblige. - */ - childrel->reltarget->exprs = (List *) - adjust_appendrel_attrs(root, - (Node *) rel->reltarget->exprs, - 1, &appinfo); - - set_dummy_rel_pathlist(childrel); - continue; - } - - /* - * Copy/Modify targetlist. Even if this child is deemed empty, we need - * its targetlist in case it falls on nullable side in a child-join - * because of partitionwise join. - * - * NB: the resulting childrel->reltarget->exprs may contain arbitrary - * expressions, which otherwise would not occur in a rel's targetlist. - * Code that might be looking at an appendrel child must cope with - * such. (Normally, a rel's targetlist would only include Vars and - * PlaceHolderVars.) XXX we do not bother to update the cost or width - * fields of childrel->reltarget; not clear if that would be useful. - */ - if (rootParentRTindex == root->parse->resultRelation) - { - Query *parse, - *orig_parse = root->parse; - List *tlist; - List *other_exprs; - ListCell *lc2; - - parse = (Query *) - adjust_appendrel_attrs(root, - (Node *) root->parse, - 1, &appinfo); - root->parse = parse; - tlist = preprocess_targetlist(root); - build_base_rel_tlists(root, tlist); - root->parse = orig_parse; - - /* - * Now add members of parent's reltarget->exprs, not already in - * child's. - */ - other_exprs = (List *) - adjust_appendrel_attrs(root, - (Node *) rel->reltarget->exprs, - 1, &appinfo); - foreach(lc2, other_exprs) - { - if (!list_member(childrel->reltarget->exprs, lfirst(lc2))) - childrel->reltarget->exprs = - lappend(childrel->reltarget->exprs, - lfirst(lc2)); - } - } - else - childrel->reltarget->exprs = (List *) - adjust_appendrel_attrs(root, - (Node *) rel->reltarget->exprs, - 1, &appinfo); + if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) + add_child_rel_equivalences(root, appinfo, rel, childrel); + childrel->has_eclass_joins = rel->has_eclass_joins; /* CE failed, so finish copying/modifying join quals. */ childrel->joininfo = (List *) diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index beb3e95101..e7927cc136 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -16,6 +16,7 @@ #include "catalog/pg_type.h" #include "catalog/pg_class.h" +#include "catalog/pg_inherits.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" @@ -145,47 +146,29 @@ add_rel_partitions_to_query(PlannerInfo *root, RelOptInfo *rel, bool scan_all_parts, Bitmapset *partindexes) { - int new_size; - int num_added_parts; - int i; + int i; + Index rootRTindex; + PlanRowMark *rootrc = NULL; - Assert(partindexes != NULL || scan_all_parts); + rootRTindex = get_inheritance_root_parent(root, rel); + if (root->rowMarks) + rootrc = get_plan_rowmark(root->rowMarks, rootRTindex); - /* Expand the PlannerInfo arrays to hold new partition objects. */ - num_added_parts = scan_all_parts ? rel->nparts : - bms_num_members(partindexes); - new_size = root->simple_rel_array_size + num_added_parts; - root->simple_rte_array = (RangeTblEntry **) - repalloc(root->simple_rte_array, - sizeof(RangeTblEntry *) * new_size); - root->simple_rel_array = (RelOptInfo **) - repalloc(root->simple_rel_array, - sizeof(RelOptInfo *) * new_size); - if (root->append_rel_array) - root->append_rel_array = (AppendRelInfo **) - repalloc(root->append_rel_array, - sizeof(AppendRelInfo *) * new_size); - else - root->append_rel_array = (AppendRelInfo **) - palloc0(sizeof(AppendRelInfo *) * - new_size); + expand_planner_arrays_for_inheritance(root, + scan_all_parts ? rel->nparts : + bms_num_members(partindexes)); - /* Set the contents of just allocated memory to 0. */ - MemSet(root->simple_rte_array + root->simple_rel_array_size, - 0, sizeof(RangeTblEntry *) * num_added_parts); - MemSet(root->simple_rel_array + root->simple_rel_array_size, - 0, sizeof(RelOptInfo *) * num_added_parts); - MemSet(root->append_rel_array + root->simple_rel_array_size, - 0, sizeof(AppendRelInfo *) * num_added_parts); - root->simple_rel_array_size = new_size; - - /* And add the partitions. */ + /* And add the partitions to the relation. */ + rel->part_rels = (RelOptInfo **) + palloc0(sizeof(RelOptInfo *) * rel->nparts); if (scan_all_parts) { for (i = 0; i < rel->nparts; i++) { rel->part_rels[i] = build_partition_rel(root, rel, - rel->part_oids[i]); + rel->part_oids[i], + rootRTindex, + rootrc); rel->live_parts = bms_add_member(rel->live_parts, i); } } @@ -195,8 +178,12 @@ add_rel_partitions_to_query(PlannerInfo *root, RelOptInfo *rel, i = -1; while ((i = bms_next_member(partindexes, i)) >= 0) rel->part_rels[i] = build_partition_rel(root, rel, - rel->part_oids[i]); + rel->part_oids[i], + rootRTindex, + rootrc); } + + } /***************************************************************************** @@ -683,6 +670,7 @@ create_lateral_join_info(PlannerInfo *root) } } +#ifdef NOT_USED /* * Lastly, propagate lateral_relids and lateral_referencers from appendrel * parent rels to their child rels. We intentionally give each child rel @@ -740,6 +728,7 @@ create_lateral_join_info(PlannerInfo *root) } } } +#endif } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index a757aba551..556eb64ae4 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -713,16 +713,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, preprocess_rowmarks(root); /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain base relations not joins, so it's OK (and marginally more - * efficient) to do it after checking for join RTEs. We must do it after - * pulling up subqueries, else we'd fail to handle inherited tables in - * subqueries. - */ - expand_inherited_tables(root); - - /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -2369,7 +2359,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = has_subclass(rte->relid); prowmarks = lappend(prowmarks, newrc); } @@ -2394,7 +2384,9 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? + has_subclass(rte->relid) : + false; prowmarks = lappend(prowmarks, newrc); } @@ -5871,8 +5863,19 @@ plan_create_index_workers(Oid tableOid, Oid indexOid) rte->inFromCl = true; query->rtable = list_make1(rte); - /* Set up RTE/RelOptInfo arrays */ - setup_simple_rel_arrays(root); + /* Set up RTE/RelOptInfo arrays. */ + + /* Arrays are accessed using RT indexes (1..N) */ + root->simple_rel_array_size = list_length(query->rtable) + 1; + + /* simple_rel_array is initialized to all NULLs */ + root->simple_rel_array = (RelOptInfo **) + palloc0(root->simple_rel_array_size * sizeof(RelOptInfo *)); + + /* simple_rte_array is an array equivalent of the rtable list */ + root->simple_rte_array = (RangeTblEntry **) + palloc0(root->simple_rel_array_size * sizeof(RangeTblEntry *)); + root->simple_rte_array[1] = rte; /* Build RelOptInfo */ rel = build_simple_rel(root, 1, NULL); diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 4d9583e63b..400568a9e7 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -39,15 +39,20 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" +#include "optimizer/plancat.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" +#include "optimizer/var.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" +#include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" #include "utils/lsyscache.h" #include "utils/partcache.h" @@ -101,12 +106,6 @@ static List *generate_append_tlist(List *colTypes, List *colCollations, List *input_tlists, List *refnames_tlist); static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist); -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); -static void make_inh_translation_list(TupleDesc old_tupdesc, - TupleDesc new_tupdesc, - RangeTblEntry *oldrte, RangeTblEntry *newrte, - Index newvarno, List **translated_vars); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, @@ -1452,37 +1451,6 @@ generate_setop_grouplist(SetOperationStmt *op, List *targetlist) return grouplist; } - -/* - * expand_inherited_tables - * Expand each rangetable entry that represents an inheritance set - * into an "append relation". At the conclusion of this process, - * the "inh" flag is set in all and only those RTEs that are append - * relation parents. - */ -void -expand_inherited_tables(PlannerInfo *root) -{ - Index nrtes; - Index rti; - ListCell *rl; - - /* - * expand_inherited_rtentry may add RTEs to parse->rtable. The function is - * expected to recursively handle any RTEs that it creates with inh=true. - * So just scan as far as the original end of the rtable list. - */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) - { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); - - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); - } -} - /* * expand_inherited_rtentry * Check whether a rangetable entry represents an inheritance set. @@ -1503,30 +1471,34 @@ expand_inherited_tables(PlannerInfo *root) * Since a partitioned table is not scanned, it might have only one associated * AppendRelInfo. */ -static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +void +expand_inherited_rtentry(PlannerInfo *root, + RelOptInfo *rel, + RangeTblEntry *rte, + Index rti) { Query *parse = root->parse; - Oid parentOID; PlanRowMark *oldrc; - Relation oldrelation; LOCKMODE lockmode; List *inhOIDs; - ListCell *l; - List *appinfos = NIL; + ListCell *l, + *l2; + List *childrtes = NIL, + *appinfos = NIL; + int num_children = 0; - /* Does RT entry allow inheritance? */ - if (!rte->inh) + Assert(rte->inh); + /* Inheritance parent or UNION ALL parent query. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + /* No partitioned tables though. */ + Assert(rte->relkind != RELKIND_PARTITIONED_TABLE); + + /* Nothing to do for already-expanded UNION ALL nodes. */ + if (rte->rtekind == RTE_SUBQUERY) return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } + /* Fast path for common case of childless table */ - parentOID = rte->relid; - if (!has_subclass(parentOID)) + if (!has_subclass(rte->relid)) { /* Clear flag before returning */ rte->inh = false; @@ -1555,15 +1527,14 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) lockmode = AccessShareLock; /* Scan for all members of inheritance set, acquire needed locks */ - if (rte->relkind != RELKIND_PARTITIONED_TABLE) - inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); + inhOIDs = find_all_inheritors(rte->relid, lockmode, NULL); /* * Check that there's at least one descendant, else treat as no-child * case. This could happen despite above has_subclass() check, if table * once had a child but no longer does. */ - if (rte->relkind != RELKIND_PARTITIONED_TABLE && list_length(inhOIDs) < 2) + if (list_length(inhOIDs) < 2) { /* Clear flag before returning */ rte->inh = false; @@ -1578,33 +1549,145 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) if (oldrc) oldrc->isParent = true; - /* Partitioned tables are expanded elsewhere. */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - return; - - /* - * Must open the parent relation to examine its tupdesc. We need not lock - * it; we assume the rewriter already did. - */ - oldrelation = heap_open(parentOID, NoLock); - foreach(l, inhOIDs) { Oid childOID = lfirst_oid(l); + Relation childrel; Index childRTindex = 0; RangeTblEntry *childrte = NULL; - AppendRelInfo *appinfo = NULL; + AppendRelInfo *appinfo; + List *childquals; + bool have_const_false_cq; + ListCell *lc; - add_inheritance_child_to_query(root, rte, rti, - oldrelation->rd_rel->reltype, - RelationGetDescr(oldrelation), - oldrc, childOID, NoLock, - &appinfo, &childrte, - &childRTindex, NULL); + /* find_all_inheritors already locked. */ + childrel = heap_open(childOID, NoLock); + + /* + * If childrel doesn't belong to this session, skip it, also + * relinquishing the lock. + */ + if (RELATION_IS_OTHER_TEMP(childrel)) + { + heap_close(childrel, lockmode); + continue; + } + + /* + * Initialize the AppendRelInfo that's used to map the child to its + * parent in subsequent planning steps. + */ + appinfo = makeNode(AppendRelInfo); + appinfo->parent_relid = rti; + + /* + * We'll replace it with childRTindex once we're done adding the child + * to the range table. + */ + appinfo->child_relid = rti; + appinfo->parent_reltype = rel->reltype; + appinfo->child_reltype = childrel->rd_rel->reltype; + make_inh_translation_list(rel->tupdesc, + RelationGetDescr(childrel), + rte->relid, childOID, rti, + &appinfo->translated_vars); + appinfo->parent_reloid = rte->relid; + + /* + * Check if we don't need to include this child based on whether its + * constraints contradict query's quals. For a child that's not same + * as the parent, we'll need to convert the quals' Vars so that they + * contain child's attribute numbers. + */ + childquals = NIL; + have_const_false_cq = false; + foreach(lc, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual = (Node *) rinfo->clause; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + if (childOID != rte->relid) + childqual = adjust_appendrel_attrs(root, childqual, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + } + } + + /* + * Mark the child as empty in certain cases, such as if a restriction + * clause reduced to constant FALSE or NULL, or if constaint exclusion + * pruned it. Note that we pass 'rti' for RT index that will be used + * to initialize constraint expressions, because we haven't initialized + * one for child yet. + */ + if (have_const_false_cq || + relation_excluded_by_constraints(root, childquals, rti, childOID, + true)) + { + heap_close(childrel, NoLock); + continue; + } + + childRTindex = add_inheritance_child_to_query(root, rte, childrel, + appinfo, oldrc, + &childrte); + heap_close(childrel, NoLock); + + /* Fix up the AppendRelInfo using the child's actual RT index. */ Assert(childRTindex > 1); - Assert(childrte != NULL); - Assert(appinfo != NULL); + appinfo->child_relid = childRTindex; + ChangeVarNodes((Node *) appinfo->translated_vars, rti, childRTindex, 0); + + /* Will be added to planner arrays below. */ + childrtes = lappend(childrtes, childrte); appinfos = lappend(appinfos, appinfo); + + /* We'll scan this child. */ + num_children++; + } + + if (num_children == 0) + { + set_dummy_rel_pathlist(rel); + return; } /* @@ -1620,95 +1703,28 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) root->append_rel_list = list_concat(root->append_rel_list, appinfos); - heap_close(oldrelation, NoLock); + expand_planner_arrays_for_inheritance(root, num_children); + forboth(l, childrtes, l2, appinfos) + { + RangeTblEntry *childrte = lfirst(l); + AppendRelInfo *appinfo = lfirst(l2); + + Assert(appinfo->child_relid < root->simple_rel_array_size); + root->simple_rte_array[appinfo->child_relid] = childrte; + root->append_rel_array[appinfo->child_relid] = appinfo; + } } -/* - * add_inheritance_child_to_query - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark for a child relation. - * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) - * - * PlanRowMarks still carry the top-parent's RTI, and the top-parent's - * allMarkTypes field still accumulates values from all descendents. - * - * "parentrte" and "parentRTindex" are immediate parent's RTE and - * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. - */ -void -add_inheritance_child_to_query(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Oid parentRelType, - TupleDesc parentDesc, +Index +add_inheritance_child_to_query(PlannerInfo *root, + RangeTblEntry *parentrte, + Relation childrel, + AppendRelInfo *appinfo, PlanRowMark *top_parentrc, - Oid childOID, int lockmode, - AppendRelInfo **appinfo_p, - RangeTblEntry **childrte_p, - Index *childRTindex_p, - PlanRowMark **child_rowmark) + RangeTblEntry **childrte_p) { - Query *parse = root->parse; - Oid parentOID = parentrte->relid; + Query *parse = root->parse; RangeTblEntry *childrte; - Index childRTindex; - AppendRelInfo *appinfo; - Relation childrel = NULL; - char child_relkind; - Oid child_reltype; - TupleDesc childDesc; - - *appinfo_p = NULL; - *childrte_p = NULL; - *childRTindex_p = 0; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - { - childrel = heap_open(childOID, lockmode); - - /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. - */ - if (RELATION_IS_OTHER_TEMP(childrel)) - { - if (childrel->rd_rel->relispartition) - elog(ERROR, "temporary relation from another session found as partition"); - heap_close(childrel, lockmode); - return; - } - - child_relkind = childrel->rd_rel->relkind; - - /* - * No point in adding to the query a partitioned table that has no - * partitions. - */ - if (child_relkind == RELKIND_PARTITIONED_TABLE && - RelationGetPartitionDesc(childrel)->nparts == 0) - { - heap_close(childrel, lockmode); - return; - } - - child_reltype = childrel->rd_rel->reltype; - childDesc = RelationGetDescr(childrel); - } - else - { - child_relkind = parentrte->relkind; - child_reltype = parentRelType; - childDesc = parentDesc; - } /* * Build an RTE for the child, and attach to query's rangetable list. We @@ -1723,11 +1739,10 @@ add_inheritance_child_to_query(PlannerInfo *root, RangeTblEntry *parentrte, * restriction clauses, so we don't need to do it here. */ childrte = copyObject(parentrte); - *childrte_p = childrte; - childrte->relid = childOID; - childrte->relkind = child_relkind; + childrte->relid = RelationGetRelid(childrel); + childrte->relkind = RelationGetForm(childrel)->relkind; /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && + if (childrte->relid != parentrte->relid && childrte->relkind == RELKIND_PARTITIONED_TABLE) childrte->inh = true; else @@ -1735,62 +1750,36 @@ add_inheritance_child_to_query(PlannerInfo *root, RangeTblEntry *parentrte, childrte->requiredPerms = 0; childrte->securityQuals = NIL; parse->rtable = lappend(parse->rtable, childrte); - childRTindex = list_length(parse->rtable); - *childRTindex_p = childRTindex; /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) + if (childrte->relid != parentrte->relid) { - appinfo = makeNode(AppendRelInfo); - appinfo->parent_relid = parentRTindex; - appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentRelType; - appinfo->child_reltype = child_reltype; - make_inh_translation_list(parentDesc, childDesc, - parentrte, childrte, childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = parentOID; - *appinfo_p = appinfo; - - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); } - /* - * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE. - */ if (top_parentrc) { PlanRowMark *childrc = makeNode(PlanRowMark); + int orig_allMarkTypes = top_parentrc->allMarkTypes; - childrc->rti = childRTindex; + childrc->rti = list_length(parse->rtable); childrc->prti = top_parentrc->rti; childrc->rowmarkId = top_parentrc->rowmarkId; /* Reselect rowmark type, because relkind might not match parent */ - childrc->markType = select_rowmark_type(childrte, - top_parentrc->strength); + childrc->markType = select_rowmark_type(childrte, top_parentrc->strength); childrc->allMarkTypes = (1 << childrc->markType); childrc->strength = top_parentrc->strength; childrc->waitPolicy = top_parentrc->waitPolicy; @@ -1804,18 +1793,65 @@ add_inheritance_child_to_query(PlannerInfo *root, RangeTblEntry *parentrte, /* Include child's rowmark type in top parent's allMarkTypes */ top_parentrc->allMarkTypes |= childrc->allMarkTypes; - root->rowMarks = lappend(root->rowMarks, childrc); - if (child_rowmark) - *child_rowmark = childrc; + + /* + * Update query's targetlist if the newly added child row mark requires + * additional columns and also add the relevant expressions to root + * parent's reltarget. + */ + if (top_parentrc->allMarkTypes != orig_allMarkTypes) + { + List *tlist = root->parse->targetList; + + tlist = add_rowmark_columns(root, tlist, list_make1(top_parentrc)); + build_base_rel_tlists(root, tlist); + } } - /* Close child relations, but keep locks */ - if (childOID != parentOID) + *childrte_p = childrte; + return list_length(parse->rtable); +} + +/* + * Starting with 'rel', find the root inheritance parent's RT index. + */ +Index +get_inheritance_root_parent(PlannerInfo *root, RelOptInfo *rel) +{ + Index rootRTindex = 0; + + /* No AppendRelInfo array yet. */ + if (root->append_rel_array == NULL) + return rel->relid; + + /* + * The inheritance root itself might be a child of UNION ALL subquery, + * so we must resort to finding it like this. + */ + rootRTindex = rel->relid; + if (root->append_rel_array[rootRTindex]) { - Assert(childrel != NULL); - heap_close(childrel, lockmode); + AppendRelInfo *appinfo = root->append_rel_array[rootRTindex]; + RangeTblEntry *parent = root->simple_rte_array[rootRTindex]; + + /* + * Keep moving up until we reach the parent RTE that's not a + * RTE_RELATION. The last RTE_RELATION entry would be the + * inheritance root's. + */ + while(parent->rtekind == RTE_RELATION) + { + appinfo = root->append_rel_array[appinfo->parent_relid]; + if (appinfo == NULL) + break; + rootRTindex = appinfo->parent_relid; + parent = root->simple_rte_array[rootRTindex]; + } } + + Assert(rootRTindex > 0); + return rootRTindex; } /* @@ -1825,13 +1861,12 @@ add_inheritance_child_to_query(PlannerInfo *root, RangeTblEntry *parentrte, * * For paranoia's sake, we match type/collation as well as attribute name. */ -static void +void make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, - RangeTblEntry *oldrte, RangeTblEntry *newrte, + Oid from_rel, Oid to_rel, Index newvarno, List **translated_vars) { List *vars = NIL; - Oid new_relid = newrte->relid; int oldnatts = old_tupdesc->natts; int newnatts = new_tupdesc->natts; int old_attno; @@ -1861,7 +1896,7 @@ make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, * When we are generating the "translation list" for the parent table * of an inheritance set, no need to search for matches. */ - if (oldrte->relid == newrte->relid) + if (from_rel == to_rel) { vars = lappend(vars, makeVar(newvarno, (AttrNumber) (old_attno + 1), @@ -1887,10 +1922,10 @@ make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, { HeapTuple newtup; - newtup = SearchSysCacheAttName(new_relid, attname); + newtup = SearchSysCacheAttName(to_rel, attname); if (!newtup) elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, get_rel_name(newrte->relid)); + attname, get_rel_name(to_rel)); new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; ReleaseSysCache(newtup); @@ -1900,10 +1935,10 @@ make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, /* Found it, check type and collation match */ if (atttypid != att->atttypid || atttypmod != att->atttypmod) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, get_rel_name(newrte->relid)); + attname, get_rel_name(to_rel)); if (attcollation != att->attcollation) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, get_rel_name(newrte->relid)); + attname, get_rel_name(to_rel)); vars = lappend(vars, makeVar(newvarno, (AttrNumber) (new_attno + 1), diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 77ae4b538a..c4a485af91 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -65,7 +65,7 @@ static bool infer_collation_opclass_match(InferenceElem *elem, Relation idxRel, List *idxExprs); static int32 get_rel_data_width(Relation rel, int32 *attr_widths); static List *get_relation_constraints(PlannerInfo *root, - Oid relationObjectId, RelOptInfo *rel, + Oid relationObjectId, Index varno, bool include_notnull); static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index, Relation heapRelation); @@ -99,14 +99,13 @@ static void set_baserel_partition_key_exprs(Relation relation, * cases these are left as zeroes, but sometimes we need to compute attr * widths here, and we may as well cache the results for costsize.c. * - * If inhparent is true, all we need to do is set up the attr arrays: + * If rte->inh is true, all we need to do is set up the attr arrays: * the RelOptInfo actually represents the appendrel formed by an inheritance * tree, and so the parent rel's physical size and index information isn't * important for it. */ void -get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - Bitmapset *updatedCols, RelOptInfo *rel) +get_relation_info(PlannerInfo *root, RangeTblEntry *rte, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; @@ -118,7 +117,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * the rewriter or when expand_inherited_rtentry() added it to the query's * rangetable. */ - relation = heap_open(relationObjectId, NoLock); + relation = heap_open(rte->relid, NoLock); /* Temporary and unlogged relations are inaccessible during recovery. */ if (!RelationNeedsWAL(relation) && RecoveryInProgress()) @@ -142,7 +141,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * must leave it zero for now to avoid bollixing the total_table_pages * calculation. */ - if (!inhparent) + if (!rte->inh) estimate_rel_size(relation, rel->attr_widths - rel->min_attr, &rel->pages, &rel->tuples, &rel->allvisfrac); @@ -153,7 +152,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * Make list of indexes. Ignore indexes on system catalogs if told to. * Don't bother with indexes for an inheritance parent, either. */ - if (inhparent || + if (rte->inh || (IgnoreSystemIndexes && IsSystemRelation(relation))) hasindex = false; else @@ -442,18 +441,18 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, } /* Collect info about relation's foreign keys, if relevant */ - get_relation_foreign_keys(root, rel, relation, inhparent); + get_relation_foreign_keys(root, rel, relation, rte->inh); /* * Collect info about relation's partitioning scheme, if any. Only * inheritance parents may be partitioned. */ - if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + if (rte->inh && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { set_relation_partition_info(root, rel, relation); if (!root->partColsUpdated) root->partColsUpdated = - has_partition_attrs(relation, updatedCols, NULL); + has_partition_attrs(relation, rte->updatedCols, NULL); } rel->tupdesc = RelationGetDescr(relation); @@ -467,7 +466,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * removing an index, or adding a hypothetical index to the indexlist. */ if (get_relation_info_hook) - (*get_relation_info_hook) (root, relationObjectId, inhparent, rel); + (*get_relation_info_hook) (root, rte->relid, rte->inh, rel); } /* @@ -1181,11 +1180,10 @@ get_relation_data_width(Oid relid, int32 *attr_widths) */ static List * get_relation_constraints(PlannerInfo *root, - Oid relationObjectId, RelOptInfo *rel, + Oid relationObjectId, Index varno, bool include_notnull) { List *result = NIL; - Index varno = rel->relid; Relation relation; TupleConstr *constr; @@ -1363,16 +1361,16 @@ get_relation_statistics(RelOptInfo *rel, Relation relation) */ bool relation_excluded_by_constraints(PlannerInfo *root, - RelOptInfo *rel, RangeTblEntry *rte) + List *baserestrictinfo, + Index varno, + Oid table_oid, + bool is_child) { List *safe_restrictions; List *constraint_pred; List *safe_constraints; ListCell *lc; - /* As of now, constraint exclusion works only with simple relations. */ - Assert(IS_SIMPLE_REL(rel)); - /* * Regardless of the setting of constraint_exclusion, detect * constant-FALSE-or-NULL restriction clauses. Because const-folding will @@ -1382,9 +1380,9 @@ relation_excluded_by_constraints(PlannerInfo *root, * this, we'd miss some optimizations that 9.5 and earlier found via much * more roundabout methods.) */ - if (list_length(rel->baserestrictinfo) == 1) + if (list_length(baserestrictinfo) == 1) { - RestrictInfo *rinfo = (RestrictInfo *) linitial(rel->baserestrictinfo); + RestrictInfo *rinfo = (RestrictInfo *) linitial(baserestrictinfo); Expr *clause = rinfo->clause; if (clause && IsA(clause, Const) && @@ -1407,7 +1405,7 @@ relation_excluded_by_constraints(PlannerInfo *root, * When constraint_exclusion is set to 'partition' we only handle * OTHER_MEMBER_RELs. */ - if (rel->reloptkind != RELOPT_OTHER_MEMBER_REL) + if (!is_child) return false; break; @@ -1424,7 +1422,7 @@ relation_excluded_by_constraints(PlannerInfo *root, * expecting to see any in its predicate argument. */ safe_restrictions = NIL; - foreach(lc, rel->baserestrictinfo) + foreach(lc, baserestrictinfo) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); @@ -1440,24 +1438,10 @@ relation_excluded_by_constraints(PlannerInfo *root, return true; /* - * Only plain relations have constraints. In a partitioning hierarchy, - * but not with regular table inheritance, it's OK to assume that any - * constraints that hold for the parent also hold for every child; for - * instance, table inheritance allows the parent to have constraints - * marked NO INHERIT, but table partitioning does not. We choose to check - * whether the partitioning parents can be excluded here; doing so - * consumes some cycles, but potentially saves us the work of excluding - * each child individually. - */ - if (rte->rtekind != RTE_RELATION || - (rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE)) - return false; - - /* * OK to fetch the constraint expressions. Include "col IS NOT NULL" * expressions for attnotnull columns, in case we can refute those. */ - constraint_pred = get_relation_constraints(root, rte->relid, rel, true); + constraint_pred = get_relation_constraints(root, table_oid, varno, true); /* * We do not currently enforce that CHECK constraints contain only @@ -1488,7 +1472,7 @@ relation_excluded_by_constraints(PlannerInfo *root, * We need strong refutation because we have to prove that the constraints * would yield false, not just NULL. */ - if (predicate_refuted_by(safe_constraints, rel->baserestrictinfo, false)) + if (predicate_refuted_by(safe_constraints, baserestrictinfo, false)) return true; return false; diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index e98b626395..f87283e261 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -16,7 +16,10 @@ #include <limits.h> +#include "access/heapam.h" +#include "catalog/partition.h" #include "catalog/pg_class.h" +#include "catalog/pg_inherits.h" #include "miscadmin.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" @@ -24,12 +27,15 @@ #include "optimizer/paths.h" #include "optimizer/placeholder.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" #include "partitioning/partbounds.h" +#include "rewrite/rewriteManip.h" #include "storage/lockdefs.h" #include "utils/hsearch.h" +#include "utils/rel.h" typedef struct JoinHashEntry @@ -91,6 +97,13 @@ setup_simple_rel_arrays(PlannerInfo *root) { RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); + /* + * If a table has no inheritance children, then turn off + * inheritance so that further plannig steps process it as a + * regular table. + */ + if (rte->rtekind == RTE_RELATION && !has_subclass(rte->relid)) + rte->inh = false; root->simple_rte_array[rti++] = rte; } } @@ -227,8 +240,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rte->updatedCols, - rel); + get_relation_info(root, rte, rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -269,29 +281,17 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); +#ifdef NOT_USED /* * If this rel is an appendrel parent, recurse to build "other rel" * RelOptInfos for its children. They are "other rels" because they are * not in the main join tree, but we will need RelOptInfos to plan access * to them. */ - if (rte->inh) + if (rte->inh && rte->rtekind == RTE_SUBQUERY) { ListCell *l; - /* - * For partitioned tables, we just allocate space for RelOptInfo's. - * pointers for all partitions and copy the partition OIDs from the - * relcache. Actual RelOptInfo is built for a partition only if it is - * not pruned. - */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - { - rel->part_rels = (RelOptInfo **) - palloc0(sizeof(RelOptInfo *) * rel->nparts); - return rel; - } - foreach(l, root->append_rel_list) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); @@ -303,6 +303,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) (void) build_simple_rel(root, appinfo->child_relid, rel); } } +#endif return rel; } @@ -1746,6 +1747,60 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, } /* + * build_child_join_reltarget + * Set up a child-join relation's reltarget from a parent-join relation. + */ +static void +build_child_join_reltarget(PlannerInfo *root, + RelOptInfo *parentrel, + RelOptInfo *childrel, + int nappinfos, + AppendRelInfo **appinfos) +{ + /* Build the targetlist */ + childrel->reltarget->exprs = (List *) + adjust_appendrel_attrs(root, + (Node *) parentrel->reltarget->exprs, + nappinfos, appinfos); + + /* Set the cost and width fields */ + childrel->reltarget->cost.startup = parentrel->reltarget->cost.startup; + childrel->reltarget->cost.per_tuple = parentrel->reltarget->cost.per_tuple; + childrel->reltarget->width = parentrel->reltarget->width; +} + +/* Expand the PlannerInfo arrays to hold new inheritance children objects. */ +void +expand_planner_arrays_for_inheritance(PlannerInfo *root, int num_children) +{ + int new_size = root->simple_rel_array_size + num_children; + + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + if (root->append_rel_array) + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + else + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + + /* Set the contents of just allocated memory to 0. */ + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * num_children); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * num_children); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * num_children); + root->simple_rel_array_size = new_size; +} + +/* * build_dummy_partition_rel * Build a RelOptInfo and AppendRelInfo for a pruned partition * @@ -1804,55 +1859,20 @@ build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx) * range table and creating planner data structures for it */ RelOptInfo * -build_partition_rel(PlannerInfo *root, RelOptInfo *parent, Oid partoid) +build_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + Oid partoid, + Index rootRTindex, + PlanRowMark *rootrc) { RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; - RelOptInfo *result; - Index rootRTindex = 0; + RangeTblEntry *partrte; + Relation partrel; + PartitionDesc partdesc; Index partRTindex = 0; - RangeTblEntry *partrte = NULL; - AppendRelInfo *appinfo = NULL; - PlanRowMark *rootrc = NULL; - int orig_allMarkTypes; - PlanRowMark *childrc = NULL; + AppendRelInfo *appinfo; int lockmode; - /* Locate the root partitioned table and fetch its PlanRowMark, if any. */ - if (root->rowMarks) - { - /* - * The root partitioned table itself might be a child of UNION ALL - * parent, so we must resort to finding the root parent like this. - */ - rootRTindex = parent->relid; - if (root->append_rel_array[rootRTindex]) - { - AppendRelInfo *tmp = root->append_rel_array[rootRTindex]; - - /* - * Keep moving up until we each the parent rel that's not a - * partitioned table. The one before that one would be the root - * parent. - */ - while(root->simple_rel_array[rootRTindex]->part_scheme) - { - tmp = root->append_rel_array[tmp->parent_relid]; - if (tmp == NULL) - break; - rootRTindex = tmp->parent_relid; - } - } - - rootrc = get_plan_rowmark(root->rowMarks, rootRTindex); - - /* - * Addition of certain child rowmarks will change the value, in which - * case, we need to add additional columns to the query's targetlist. - */ - if (rootrc) - orig_allMarkTypes = rootrc->allMarkTypes; - } - /* Determine the correct lockmode to use. */ if (rootRTindex == root->parse->resultRelation) lockmode = RowExclusiveLock; @@ -1860,58 +1880,124 @@ build_partition_rel(PlannerInfo *root, RelOptInfo *parent, Oid partoid) lockmode = RowShareLock; else lockmode = AccessShareLock; - add_inheritance_child_to_query(root, parentrte, parent->relid, - parent->reltype, parent->tupdesc, - rootrc, partoid, lockmode, - &appinfo, &partrte, &partRTindex, - &childrc); - /* Partition turned out to be a partitioned table with 0 partitions. */ - if (partrte == NULL) + partrel = heap_open(partoid, lockmode); + Assert(!RELATION_IS_OTHER_TEMP(partrel)); + partdesc = RelationGetPartitionDesc(partrel); + if (partdesc && partdesc->nparts == 0) + { + heap_close(partrel, NoLock); return NULL; + } + + appinfo = makeNode(AppendRelInfo); + appinfo->parent_relid = parent->relid; + appinfo->child_relid = 0; /* Set to correct value below. */ + appinfo->parent_reltype = parent->reltype; + appinfo->child_reltype = partrel->rd_rel->reltype; + make_inh_translation_list(parent->tupdesc, + RelationGetDescr(partrel), + parentrte->relid, partoid, + parent->relid, + &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; + + partRTindex = add_inheritance_child_to_query(root, parentrte, partrel, + appinfo, rootrc, &partrte); + heap_close(partrel, NoLock); + + Assert(partrte != NULL); + + /* Fix up the AppendRelInfo with correct child RT index. */ + appinfo->child_relid = partRTindex; + ChangeVarNodes((Node *) appinfo->translated_vars, parent->relid, + partRTindex, 0); - Assert(appinfo != NULL); root->append_rel_list = lappend(root->append_rel_list, appinfo); root->simple_rte_array[partRTindex] = partrte; root->append_rel_array[partRTindex] = appinfo; - /* Build the RelOptInfo. */ - result = build_simple_rel(root, partRTindex, parent); - - /* Set the information created by create_lateral_join_info(). */ - result->direct_lateral_relids = parent->direct_lateral_relids; - result->lateral_relids = parent->lateral_relids; - result->lateral_referencers = parent->lateral_referencers; - - if (rootrc && rootrc->allMarkTypes != orig_allMarkTypes) - { - List *tlist = root->parse->targetList; - - tlist = add_rowmark_columns(root, tlist, list_make1(rootrc)); - } - - return result; + return build_inheritance_child_rel(root, partRTindex, parent); } -/* - * build_child_join_reltarget - * Set up a child-join relation's reltarget from a parent-join relation. - */ -static void -build_child_join_reltarget(PlannerInfo *root, - RelOptInfo *parentrel, - RelOptInfo *childrel, - int nappinfos, - AppendRelInfo **appinfos) +RelOptInfo * +build_inheritance_child_rel(PlannerInfo *root, Index childRTindex, + RelOptInfo *parent) { - /* Build the targetlist */ - childrel->reltarget->exprs = (List *) - adjust_appendrel_attrs(root, - (Node *) parentrel->reltarget->exprs, - nappinfos, appinfos); + Relids top_parent_relids = parent->top_parent_relids; + int rootParentRTindex = top_parent_relids ? + bms_singleton_member(top_parent_relids) : + parent->relid; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + /* Build the RelOptInfo. */ + RelOptInfo *childrel = build_simple_rel(root, childRTindex, parent); - /* Set the cost and width fields */ - childrel->reltarget->cost.startup = parentrel->reltarget->cost.startup; - childrel->reltarget->cost.per_tuple = parentrel->reltarget->cost.per_tuple; - childrel->reltarget->width = parentrel->reltarget->width; + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * Copy/Modify targetlist. Even if this child is deemed empty, we need + * its targetlist in case it falls on nullable side in a child-join + * because of partitionwise join. + * + * NB: the resulting childrel->reltarget->exprs may contain arbitrary + * expressions, which otherwise would not occur in a rel's targetlist. + * Code that might be looking at an appendrel child must cope with + * such. (Normally, a rel's targetlist would only include Vars and + * PlaceHolderVars.) XXX we do not bother to update the cost or width + * fields of childrel->reltarget; not clear if that would be useful. + */ + if (rootParentRTindex == root->parse->resultRelation) + { + Query *parse, + *orig_parse = root->parse; + List *tlist; + List *other_exprs; + ListCell *lc2; + + parse = (Query *) + adjust_appendrel_attrs(root, + (Node *) root->parse, + 1, &appinfo); + root->parse = parse; + tlist = preprocess_targetlist(root); + build_base_rel_tlists(root, tlist); + root->parse = orig_parse; + + /* + * Now add members of parent's reltarget->exprs, not already in + * child's. + */ + other_exprs = (List *) + adjust_appendrel_attrs(root, + (Node *) parent->reltarget->exprs, + 1, &appinfo); + foreach(lc2, other_exprs) + { + if (!list_member(childrel->reltarget->exprs, lfirst(lc2))) + childrel->reltarget->exprs = + lappend(childrel->reltarget->exprs, + lfirst(lc2)); + } + } + else + { + childrel->reltarget->exprs = (List *) + adjust_appendrel_attrs(root, + (Node *) parent->reltarget->exprs, + 1, &appinfo); + } + + return childrel; } diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 345ab19989..d5dad5a1e9 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -601,8 +601,6 @@ prune_append_rel_partitions(PlannerInfo *root, RelOptInfo *rel) if (bms_is_empty(partindexes)) return; } - else - scan_all_parts = true; } else scan_all_parts = true; @@ -611,7 +609,8 @@ prune_append_rel_partitions(PlannerInfo *root, RelOptInfo *rel) * Build selected partitions' range table entries, RelOptInfos, and * AppendRelInfos. */ - add_rel_partitions_to_query(root, rel, scan_all_parts, partindexes); + if (scan_all_parts || !bms_is_empty(partindexes)) + add_rel_partitions_to_query(root, rel, scan_all_parts, partindexes); } /* diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 4f567765a4..bf5a025b69 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -15,6 +15,7 @@ #define PATHNODE_H #include "nodes/bitmapset.h" +#include "nodes/plannodes.h" #include "nodes/relation.h" @@ -261,6 +262,8 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays_for_inheritance(PlannerInfo *root, + int num_children); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); @@ -300,8 +303,12 @@ extern RelOptInfo *build_child_join_rel(PlannerInfo *root, extern RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx); +RelOptInfo *build_inheritance_child_rel(PlannerInfo *root, Index childRTindex, + RelOptInfo *parent); extern RelOptInfo *build_partition_rel(PlannerInfo *root, RelOptInfo *parent, - Oid partoid); + Oid partoid, + Index rootRTindex, + PlanRowMark *rootrc); #endif /* PATHNODE_H */ diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index edaf2a3b4f..5e1e7478bf 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -25,8 +25,8 @@ typedef void (*get_relation_info_hook_type) (PlannerInfo *root, extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; -extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, Bitmapset *updatedCols, RelOptInfo *rel); +extern void get_relation_info(PlannerInfo *root, RangeTblEntry *rte, + RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); @@ -36,7 +36,10 @@ extern void estimate_rel_size(Relation rel, int32 *attr_widths, extern int32 get_relation_data_width(Oid relid, int32 *attr_widths); extern bool relation_excluded_by_constraints(PlannerInfo *root, - RelOptInfo *rel, RangeTblEntry *rte); + List *baserestrictinfo, + Index varno, + Oid table_oid, + bool is_child); extern List *build_physical_tlist(PlannerInfo *root, RelOptInfo *rel); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index ee7406e4f6..1462e7b92f 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -16,6 +16,7 @@ #include "nodes/plannodes.h" #include "nodes/relation.h" +#include "utils/relcache.h" /* @@ -50,18 +51,21 @@ extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); */ extern RelOptInfo *plan_set_operations(PlannerInfo *root); -extern void expand_inherited_tables(PlannerInfo *root); - -extern void add_inheritance_child_to_query(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Oid parentRelType, - TupleDesc parentDesc, - PlanRowMark *top_parentrc, - Oid childOID, int lockmode, - AppendRelInfo **appinfo_p, - RangeTblEntry **childrte_p, - Index *childRTindex_p, - PlanRowMark **child_rowmark); +extern void expand_inherited_rtentry(PlannerInfo *root, + RelOptInfo *rel, + RangeTblEntry *rte, + Index rti); +extern Index add_inheritance_child_to_query(PlannerInfo *root, + RangeTblEntry *parentrte, + Relation childrel, + AppendRelInfo *appinfo, + PlanRowMark *top_parentrc, + RangeTblEntry **childrte_p); +extern Index get_inheritance_root_parent(PlannerInfo *root, RelOptInfo *rel); +extern void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-09-29 13:30  Dilip Kumar <dilipbalaut@gmail.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  2 siblings, 2 replies; 265+ messages in thread

From: Dilip Kumar @ 2018-09-29 13:30 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; pgsql-hackers

On Fri, Sep 14, 2018 at 3:58 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> Thanks a lot for your detailed review.
>
I was going through your patch (v3-0002) and I have some suggestions

1.
- if (nparts > 0)
+ /*
+ * For partitioned tables, we just allocate space for RelOptInfo's.
+ * pointers for all partitions and copy the partition OIDs from the
+ * relcache.  Actual RelOptInfo is built for a partition only if it is
+ * not pruned.
+ */
+ if (rte->relkind == RELKIND_PARTITIONED_TABLE)
+ {
  rel->part_rels = (RelOptInfo **)
- palloc(sizeof(RelOptInfo *) * nparts);
+ palloc0(sizeof(RelOptInfo *) * rel->nparts);
+ return rel;
+ }

I think we can delay allocating memory for rel->part_rels?  And we can
allocate in add_rel_partitions_to_query only
for those partitions which survive pruning.

2.
add_rel_partitions_to_query
...
+ /* Expand the PlannerInfo arrays to hold new partition objects. */
+ num_added_parts = scan_all_parts ? rel->nparts :
+ bms_num_members(partindexes);
+ new_size = root->simple_rel_array_size + num_added_parts;
+ root->simple_rte_array = (RangeTblEntry **)
+ repalloc(root->simple_rte_array,
+ sizeof(RangeTblEntry *) * new_size);
+ root->simple_rel_array = (RelOptInfo **)
+ repalloc(root->simple_rel_array,
+ sizeof(RelOptInfo *) * new_size);
+ if (root->append_rel_array)
+ root->append_rel_array = (AppendRelInfo **)
+ repalloc(root->append_rel_array,
+ sizeof(AppendRelInfo *) * new_size);
+ else
+ root->append_rel_array = (AppendRelInfo **)
+ palloc0(sizeof(AppendRelInfo *) *
+ new_size);

Instead of re-pallocing for every partitioned relation can't we first
count the total number of surviving partitions and
repalloc at once.

3.
+ /*
+ * And do prunin.  Note that this adds AppendRelInfo's of only the
+ * partitions that are not pruned.
+ */

prunin/pruning

-- 
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-10-02 01:20  Michael Paquier <michael@paquier.xyz>
  parent: Dilip Kumar <dilipbalaut@gmail.com>
  1 sibling, 1 reply; 265+ messages in thread

From: Michael Paquier @ 2018-10-02 01:20 UTC (permalink / raw)
  To: Dilip Kumar <dilipbalaut@gmail.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; David Rowley <david.rowley@2ndquadrant.com>; pgsql-hackers

On Sat, Sep 29, 2018 at 07:00:02PM +0530, Dilip Kumar wrote:
> I think we can delay allocating memory for rel->part_rels?  And we can
> allocate in add_rel_partitions_to_query only
> for those partitions which survive pruning.

This last review set has not been answered, and as it is recent I am
moving the patch to next CF, waiting on author.
--
Michael

Attachments:

  signature.asc (application/pgp-signature, 832B) download

^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-10-02 01:24  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Michael Paquier <michael@paquier.xyz>
  0 siblings, 0 replies; 265+ messages in thread

From: Amit Langote @ 2018-10-02 01:24 UTC (permalink / raw)
  To: Michael Paquier <michael@paquier.xyz>; Dilip Kumar <dilipbalaut@gmail.com>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; pgsql-hackers

On 2018/10/02 10:20, Michael Paquier wrote:
> On Sat, Sep 29, 2018 at 07:00:02PM +0530, Dilip Kumar wrote:
>> I think we can delay allocating memory for rel->part_rels?  And we can
>> allocate in add_rel_partitions_to_query only
>> for those partitions which survive pruning.
> 
> This last review set has not been answered, and as it is recent I am
> moving the patch to next CF, waiting on author.

I was thinking of doing that myself today, thanks for taking care of that.

Will get back to this by the end of this week.

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-10-02 23:31  Thomas Munro <thomas.munro@enterprisedb.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  2 siblings, 1 reply; 265+ messages in thread

From: Thomas Munro @ 2018-10-02 23:31 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; pgsql-hackers

On Fri, Sep 14, 2018 at 10:29 PM Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> Attached is what I have at the moment.

I realise this is a WIP but FYI the docs don't build (you removed a
<note> element that is still needed, when removing a paragraph).

-- 
Thomas Munro
http://www.enterprisedb.com




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-10-03 00:47  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Thomas Munro <thomas.munro@enterprisedb.com>
  0 siblings, 0 replies; 265+ messages in thread

From: Amit Langote @ 2018-10-03 00:47 UTC (permalink / raw)
  To: Thomas Munro <thomas.munro@enterprisedb.com>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; pgsql-hackers

On 2018/10/03 8:31, Thomas Munro wrote:
> On Fri, Sep 14, 2018 at 10:29 PM Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> Attached is what I have at the moment.
> 
> I realise this is a WIP but FYI the docs don't build (you removed a
> <note> element that is still needed, when removing a paragraph).

Thanks Thomas for the heads up, will fix.

Regards,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2018-10-04 08:11  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  2 siblings, 2 replies; 265+ messages in thread

From: Imai, Yoshikazu @ 2018-10-04 08:11 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers; David Rowley <david.rowley@2ndquadrant.com>

Hi, Amit!

On Thu, Sept 13, 2018 at 10:29 PM, Amit Langote wrote:
> Attached is what I have at the moment.

I also do the code review of the patch.
I could only see a v3-0001.patch so far, so below are all about v3-0001.patch.

I am new to inheritance/partitioning codes and code review, so my review below might have some mistakes. If there are mistakes, please point out that kindly :)


v3-0001:
1. Is there any reason inheritance_make_rel_from_joinlist returns "parent" that is passed to it? I think we can refer parent in the caller even if inheritance_make_rel_from_joinlist returns nothing.

+static RelOptInfo *
+inheritance_make_rel_from_joinlist(PlannerInfo *root,
+								   RelOptInfo *parent,
+								   List *joinlist)
+{
    ...
+	return parent;
+}

2. Is there the possible case that IS_DUMMY_REL(child_joinrel) is true in inheritance_adjust_scanjoin_target()?

+inheritance_adjust_scanjoin_target(PlannerInfo *root,
	...
+{
	...
+	foreach(lc, root->inh_target_child_rels)
+	{
		...
+		/*
+		 * If the child was pruned, its corresponding joinrel will be empty,
+		 * which we can ignore safely.
+		 */
+		if (IS_DUMMY_REL(child_joinrel))
+			continue;

I think inheritance_make_rel_from_joinlist() doesn't make dummy joinrel for the child that was pruned.

+inheritance_make_rel_from_joinlist(PlannerInfo *root,
...
+{
	...
+	foreach(lc, root->append_rel_list)
+	{
+		RelOptInfo *childrel;
		...
+		/* Ignore excluded/pruned children. */
+		if (IS_DUMMY_REL(childrel))
+			continue;
		...
+		/*
+		 * Save child joinrel to be processed later in
+		 * inheritance_adjust_scanjoin_target, which adjusts its paths to
+		 * be able to emit expressions in query's top-level target list.
+		 */
+		root->inh_target_child_rels = lappend(root->inh_target_child_rels,
+											  childrel);
+	}
+}

3.
Is the "root->parse->commandType != CMD_INSERT" required in:

@@ -2018,13 +1514,45 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
...
+		/*
+		 * For UPDATE/DELETE on an inheritance parent, we must generate and
+		 * apply scanjoin target based on targetlist computed using each
+		 * of the child relations.
+		 */
+		if (parse->commandType != CMD_INSERT &&
+			current_rel->reloptkind == RELOPT_BASEREL &&
+			current_rel->relid == root->parse->resultRelation &&
+			root->simple_rte_array[current_rel->relid]->inh)
...

@@ -2137,92 +1665,123 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 	final_rel->fdwroutine = current_rel->fdwroutine;
 
...
-	foreach(lc, current_rel->pathlist)
+	if (current_rel->reloptkind == RELOPT_BASEREL &&
+		current_rel->relid == root->parse->resultRelation &&
+		!IS_DUMMY_REL(current_rel) &&
+		root->simple_rte_array[current_rel->relid]->inh &&
+		parse->commandType != CMD_INSERT)


I think if a condition would be "current_rel->relid == root->parse->resultRelation && parse->commandType != CMD_INSERT" at the above if clause, elog() is called in the query_planner and planner don't reach above if clause.
Of course there is the case that query_planner returns the dummy joinrel and elog is not called, but in that case, current_rel->relid may be 0(current_rel is dummy joinrel) and root->parse->resultRelation may be not 0(a query is INSERT).

4. Can't we use define value(IS_PARTITIONED or something like IS_INHERITANCE?) to identify inheritance and partitioned table in below code? It was little confusing to me that which code is related to inheritance/partitioned when looking codes.

In make_one_rel():
+	if (root->parse->resultRelation &&
+		root->simple_rte_array[root->parse->resultRelation]->inh)
+	{
...
+		rel = inheritance_make_rel_from_joinlist(root, targetrel, joinlist);

In inheritance_make_rel_from_joinlist():
+		if (childrel->part_scheme != NULL)
+			childrel =
+				inheritance_make_rel_from_joinlist(root, childrel,
+												   translated_joinlist);


I can't review inheritance_adjust_scanjoin_target() deeply, because it is difficult to me to understand fully codes about join processing.

--
Yoshikazu Imai



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-10-04 09:06  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  1 sibling, 0 replies; 265+ messages in thread

From: Amit Langote @ 2018-10-04 09:06 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: pgsql-hackers; David Rowley <david.rowley@2ndquadrant.com>

Imai-san,

On 2018/10/04 17:11, Imai, Yoshikazu wrote:
> Hi, Amit!
> 
> On Thu, Sept 13, 2018 at 10:29 PM, Amit Langote wrote:
>> Attached is what I have at the moment.
> 
> I also do the code review of the patch.

Thanks a lot for your review.  I'm working on updating the patches as
mentioned upthread and should be able to post a new version sometime next
week.  I will consider your comments along with David's and Dilip's before
posting the new patch.

Regards,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-10-15 09:24  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Dilip Kumar <dilipbalaut@gmail.com>
  1 sibling, 0 replies; 265+ messages in thread

From: Amit Langote @ 2018-10-15 09:24 UTC (permalink / raw)
  To: Dilip Kumar <dilipbalaut@gmail.com>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; pgsql-hackers

Hi Dilip,

Thanks for your review comments.  Sorry it took me a while replying to them.

On 2018/09/29 22:30, Dilip Kumar wrote:
> I was going through your patch (v3-0002) and I have some suggestions
> 
> 1.
> - if (nparts > 0)
> + /*
> + * For partitioned tables, we just allocate space for RelOptInfo's.
> + * pointers for all partitions and copy the partition OIDs from the
> + * relcache.  Actual RelOptInfo is built for a partition only if it is
> + * not pruned.
> + */
> + if (rte->relkind == RELKIND_PARTITIONED_TABLE)
> + {
>   rel->part_rels = (RelOptInfo **)
> - palloc(sizeof(RelOptInfo *) * nparts);
> + palloc0(sizeof(RelOptInfo *) * rel->nparts);
> + return rel;
> + }
> 
> I think we can delay allocating memory for rel->part_rels?  And we can
> allocate in add_rel_partitions_to_query only
> for those partitions which survive pruning.

Unfortunately, we can't do that.  The part_rels array is designed such
that there is one entry in it for each partition of a partitioned table
that physically exists.  Since the pruning code returns a set of indexes
into the array of *all* partitions, the planner's array must also be able
to hold *all* partitions, even if most of them would be NULL in the common
case.

Also, partition wise join code depends on finding a valid (even if dummy)
RelOptInfo for *all* partitions of a partitioned table to support outer
join planning.  Maybe, we can change partition wise join code to not
depend on such dummy-marked RelOptInfos on the nullable side, but until
then we'll have to leave part_rels like this.

> 2.
> add_rel_partitions_to_query
> ...
> + /* Expand the PlannerInfo arrays to hold new partition objects. */
> + num_added_parts = scan_all_parts ? rel->nparts :
> + bms_num_members(partindexes);
> + new_size = root->simple_rel_array_size + num_added_parts;
> + root->simple_rte_array = (RangeTblEntry **)
> + repalloc(root->simple_rte_array,
> + sizeof(RangeTblEntry *) * new_size);
> + root->simple_rel_array = (RelOptInfo **)
> + repalloc(root->simple_rel_array,
> + sizeof(RelOptInfo *) * new_size);
> + if (root->append_rel_array)
> + root->append_rel_array = (AppendRelInfo **)
> + repalloc(root->append_rel_array,
> + sizeof(AppendRelInfo *) * new_size);
> + else
> + root->append_rel_array = (AppendRelInfo **)
> + palloc0(sizeof(AppendRelInfo *) *
> + new_size);
> 
> Instead of re-pallocing for every partitioned relation can't we first
> count the total number of surviving partitions and
> repalloc at once.

Hmm, doing this seems a bit hard too.  Since the function to perform
partition pruning (prune_append_rel_partitions), which determines the
number of partitions that will be added, currently contains a RelOptInfo
argument for using the partitioning info created by
set_relation_partition_info, we cannot call it on a partitioned table
unless we've created a RelOptInfo for it.  So, we cannot delay creating
partition RelOptInfos to a point where we've figured out all the children,
because some of them might be partitioned tables themselves.  IOW, we must
create them as we process each partitioned table (and its partitioned
children recursively) and expand the PlannerInfo arrays as we go.

I've thought about the alternative(s) that will allow us to do what you
suggest, but it cannot be implemented without breaking how we initialize
partitioning info in RelOptInfo.  For example, we could refactor
prune_append_rel_array's interface to accept a Relation pointer instead of
RelOptInfo, but we don't have a Relation pointer handy at the point where
we can do pruning without re-opening it, which is something to avoid.
Actually, that's not the only refactoring needed as I've come to know when
trying to implement it.

On a related note, with the latest patch, I've also delayed regular
inheritance expansion as a whole, to avoid making the partitioning
expansion a special case.   That means we'll expand the planner arrays
every time a inheritance parent is encountered in the range table.  The
only aspect where partitioning becomes a special case in the new code is
that we can call the pruning code even before we've expanded the planner
arrays and the pruning limits the size to which the arrays must be
expanded to.  Regular inheritance requires that the planner arrays be
expanded by the amount given by
list_length(find_all_inheritors(root_parent_oid)).

> 3.
> + /*
> + * And do prunin.  Note that this adds AppendRelInfo's of only the
> + * partitions that are not pruned.
> + */
> 
> prunin/pruning

I've since rewritten this comment and fixed the misspelling.

I'm still working on some other comments.

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-10-16 01:58  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: David Rowley <david.rowley@2ndquadrant.com>
  1 sibling, 0 replies; 265+ messages in thread

From: Amit Langote @ 2018-10-16 01:58 UTC (permalink / raw)
  To: David Rowley <david.rowley@2ndquadrant.com>; +Cc: pgsql-hackers

Hi David,

I've managed to get back to the rest of your comments.  Sorry that it took
me a while.

On 2018/09/11 8:23, David Rowley wrote:
> On 30 August 2018 at 21:29, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> Attached updated patches, with 0002 containing the changes mentioned above.
> 
> Here's my first pass review on this:
> 
> 0002:
> 
> 10. I think moving the PlannerInfo->total_table_pages calculation
> needs to be done in its own patch. This is a behavioural change where
> we no longer count pruned relations in the calculation which can
> result in plan changes. I posted a patch in [1] to fix this as a bug
> fix as I think the current code is incorrect. My patch also updates
> the first paragraph of the comment. You've not done that.

As mentioned on the other thread, I've included your patch in my own series.

> 11. "pruning"
> 
> + * And do prunin.  Note that this adds AppendRelInfo's of only the

Fixed.

> 12. It's much more efficient just to do bms_add_range() outside the loop in:
> 
> + for (i = 0; i < rel->nparts; i++)
> + {
> + rel->part_rels[i] = build_partition_rel(root, rel,
> + rel->part_oids[i]);
> + rel->live_parts = bms_add_member(rel->live_parts, i);
> + }

You're right, I almost forgot about bms_add_range that we just recently added.

> 13. In set_append_rel_size() the foreach(l, root->append_rel_list)
> loop could be made to loop over RelOptInfo->live_parts instead which
> would save having to skip over AppendRelInfos that don't belong to
> this parent. You'd need to make live_parts more general purpose and
> also use it to mark children of inheritance parents.

Hmm, I've thought about it, but live_parts is a set of indexes into the
part_rels array, which is meaningful only for partitioned tables.

I'm not really sure if we should generalize part_rels to child_rels and
set it even for regular inheritance, if only for the efficiency of
set_append_rel_size and set_append_rel_pathlists.

In the updated patches, I've managed to make this whole effort applicable
to regular inheritance in general (as I said I would last month), where
the only special case code needed for partitioning is to utilize the
functionality of partprune.c, but I wasn't generalize everything.  We can,
as a follow on patch maybe.

> 14. I think you can skip the following if both are NULL. You could
> likely add more smarts for different join types, but I think you
> should at least skip when both are NULL. Perhaps the loop could be
> driven off of bms_intersec of the two Rel's live_parts?
> 
> + if (child_rel1 == NULL)
> + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts);
> + if (child_rel2 == NULL)
> + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts);

Actually, not needing to build the dummy partition rel here is something
we could get back to doing someday, but not in this patch.  The logic
involved to do so is too much related to join planning, so I thought we
could pursue it later.

> 15. The following is not required when append_rel_array was previously NULL.
> 
> + MemSet(root->append_rel_array + root->simple_rel_array_size,
> +    0, sizeof(AppendRelInfo *) * num_added_parts);

Yeah, I've fixed that.  If previously NULL, it's palloc'd.

> 16. I don't think scan_all_parts is worth the extra code.  The cost of
> doing bms_num_members is going to be pretty small in comparison to
> building paths for and maybe doing a join search for all partitions.
> 
> + num_added_parts = scan_all_parts ? rel->nparts :
> + bms_num_members(partindexes);
> 
> In any case, you've got a bug in prune_append_rel_partitions() where
> you're setting scan_all_parts to true instead of returning when
> contradictory == true.

I too started disliking it, so got rid of scan_all_parts.

> 17. lockmode be of type LOCKMODE, not int.
> 
> +    Oid childOID, int lockmode,

This might no longer be the same code as you saw when reviewing, but I've
changed all lockmode variables in the patch to use LOCKMODE.

> 18. Comment contradicts the code.
> 
> + /* Open rel if needed; we already have required locks */
> + if (childOID != parentOID)
> + {
> + childrel = heap_open(childOID, lockmode);
> 
> I think you should be using NoLock here.
>
> 19. Comment contradicts the code.
> 
> + /* Close child relations, but keep locks */
> + if (childOID != parentOID)
> + {
> + Assert(childrel != NULL);
> + heap_close(childrel, lockmode);
> + }

These two blocks, too.  There is no such code with the latest patch.
Code's been moved such that there is no need for such special handling.

> 20. I assume translated_vars can now be NIL due to
> build_dummy_partition_rel() not setting it.
> 
> - if (var->varlevelsup == 0 && appinfo)
> + if (var->varlevelsup == 0 && appinfo && appinfo->translated_vars)
> 
> It might be worth writing a comment to explain that, otherwise it's
> not quite clear why you're doing this.

Regarding this and and one more comment below, I've replied below.

> 21. Unrelated change;
> 
>   Assert(relid > 0 && relid < root->simple_rel_array_size);
> +

Couldn't find it in the latest patch, so must be gone.

> 22. The following comment mentions that Oids are copied, but that does
> not happen in the code.
> 
> + /*
> + * For partitioned tables, we just allocate space for RelOptInfo's.
> + * pointers for all partitions and copy the partition OIDs from the
> + * relcache.  Actual RelOptInfo is built for a partition only if it is
> + * not pruned.
> + */
> 
> The Oid copying already happened during get_relation_info().

Couldn't find this comment in the new code.

> 23. Traditionally translated_vars populated with a sequence of Vars in
> the same order to mean no translation. Here you're changing how that
> works:
> 
> + /* leaving translated_vars to NIL to mean no translation needed */
> 
> This seems to be about the extent of your documentation on this, which
> is not good enough.

I've changed the other code such that only the existing meaning of no-op
translation list is preserved, that is, the one you wrote above.  I
modified build_dummy_partition_rel such that a no-op translated vars is
built based on parent's properties, instead of leaving it NIL.  I've also
removed the special case code in adjust_appendrel_attrs_mutator which
checked translated_vars for NIL.

> 24. "each" -> "reach"? ... Actually, I don't understand the comment.
> In a partitioned hierarchy, how can the one before the top-level
> partitioned table not be a partitioned table?
> 
> /*
> * Keep moving up until we each the parent rel that's not a
> * partitioned table.  The one before that one would be the root
> * parent.
> */

This comment and the code has since been rewritten, but to clarify, this
is the new comment:

     * Figuring out if we're the root partitioned table is a bit involved,
     * because the root table mentioned in the query itself might be a
     * child of UNION ALL subquery.
     */

> 25. "already"
> 
> + * expand_inherited_rtentry alreay locked all partitions, so pass

No longer appears in the latest patch.

> 26. Your changes to make_partitionedrel_pruneinfo() look a bit broken.
> You're wrongly assuming live_parts is the same as present_parts.  If a
> CHECK constraint eliminated the partition then those will be present
> in live_parts but won't be part of the Append/MergeAppend subplans.
> You might be able to maintain some of this optimisation by checking
> for dummy rels inside the loop, but you're going to need to put back
> the code that sets present_parts.
> 
> + present_parts = bms_copy(subpart->live_parts);

You're right.  The new code structure is such that it allows to delete the
partition index of a partition that's excluded by constraints, so I fixed
it so that live_parts no longer contains the partitions that are excluded
by constraints.

The existing code that sets present parts goes through all partitions by
looping over nparts members of part_rels, which is a pattern I think we
should avoid, as I'd think you'd agree.

> 27. Comment contradicts the code:
> 
> + Bitmapset  *live_parts; /* unpruned parts; NULL if all are live */
> 
> in add_rel_partitions_to_query() you're doing:
> 
> + if (scan_all_parts)
> + {
> + for (i = 0; i < rel->nparts; i++)
> + {
> + rel->part_rels[i] = build_partition_rel(root, rel,
> + rel->part_oids[i]);
> + rel->live_parts = bms_add_member(rel->live_parts, i);
> + }
> + }
> 
> so the NULL part seems untrue.

I've rewritten the comment.

> 
> 28. Missing comments:
> 
> + TupleDesc tupdesc;
> + Oid reltype;
> 
> 29. The comment for prune_append_rel_partitions claims it "Returns RT
> indexes", but that's not the case, per:
> 
> -Relids
> -prune_append_rel_partitions(RelOptInfo *rel)
> +void
> +prune_append_rel_partitions(PlannerInfo *root, RelOptInfo *rel)

Has changed in the latest patch and now it returns a bitmapset of
partition indexes, same as what get_matching_partitions does.

> 0003:
> 
> 30. 2nd test can be tested inside the first test to remove redundant
> partition check.
> 
> - inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
> + if (rte->relkind != RELKIND_PARTITIONED_TABLE)
> + inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
> 
>   /*
>   * Check that there's at least one descendant, else treat as no-child
>   * case.  This could happen despite above has_subclass() check, if table
>   * once had a child but no longer does.
>   */
> - if (list_length(inhOIDs) < 2)
> + if (rte->relkind != RELKIND_PARTITIONED_TABLE && list_length(inhOIDs) < 2)
>   {

This code has changed quite a bit in the latest patch, so the comment no
longer applies.

> 31. The following code is wrong:
> 
> + /* Determine the correct lockmode to use. */
> + if (rootRTindex == root->parse->resultRelation)
> + lockmode = RowExclusiveLock;
> + else if (rootrc && RowMarkRequiresRowShareLock(rootrc->markType))
> + lockmode = RowShareLock;
> + else
> + lockmode = AccessShareLock;
> 
> rootRTIndex remains at 0 if there are no row marks and resultRelation
> will be 0 for SELECT queries, this means you'll use a RowExclusiveLock
> for a SELECT instead of an AccessShareLock.
>
> Why not just check the lockmode of the parent and use that?

No such logic exists anymore due to recent developments (addition of
rellockmode to RTE).


I'll post the latest patches in this week.

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-10-18 02:15  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  1 sibling, 1 reply; 265+ messages in thread

From: Amit Langote @ 2018-10-18 02:15 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: pgsql-hackers; David Rowley <david.rowley@2ndquadrant.com>

Imai-san,

Thank you for reviewing.

On 2018/10/04 17:11, Imai, Yoshikazu wrote:
> Hi, Amit!
> 
> On Thu, Sept 13, 2018 at 10:29 PM, Amit Langote wrote:
>> Attached is what I have at the moment.
> 
> I also do the code review of the patch.
> I could only see a v3-0001.patch so far, so below are all about v3-0001.patch.
> 
> I am new to inheritance/partitioning codes and code review, so my review below might have some mistakes. If there are mistakes, please point out that kindly :)
> 
> 
> v3-0001:
> 1. Is there any reason inheritance_make_rel_from_joinlist returns "parent" that is passed to it? I think we can refer parent in the caller even if inheritance_make_rel_from_joinlist returns nothing.
>
> +static RelOptInfo *
> +inheritance_make_rel_from_joinlist(PlannerInfo *root,
> +								   RelOptInfo *parent,
> +								   List *joinlist)
> +{
>     ...
> +	return parent;
> +}

There used to be a reason to do that in previous versions, but seems it
doesn't hold anymore.  I've changed it to not return any value.

> 2. Is there the possible case that IS_DUMMY_REL(child_joinrel) is true in inheritance_adjust_scanjoin_target()?
> 
> +inheritance_adjust_scanjoin_target(PlannerInfo *root,
> 	...
> +{
> 	...
> +	foreach(lc, root->inh_target_child_rels)
> +	{
> 		...
> +		/*
> +		 * If the child was pruned, its corresponding joinrel will be empty,
> +		 * which we can ignore safely.
> +		 */
> +		if (IS_DUMMY_REL(child_joinrel))
> +			continue;
> 
> I think inheritance_make_rel_from_joinlist() doesn't make dummy joinrel for the child that was pruned.
> 
> +inheritance_make_rel_from_joinlist(PlannerInfo *root,
> ...
> +{
> 	...
> +	foreach(lc, root->append_rel_list)
> +	{
> +		RelOptInfo *childrel;
> 		...
> +		/* Ignore excluded/pruned children. */
> +		if (IS_DUMMY_REL(childrel))
> +			continue;
> 		...
> +		/*
> +		 * Save child joinrel to be processed later in
> +		 * inheritance_adjust_scanjoin_target, which adjusts its paths to
> +		 * be able to emit expressions in query's top-level target list.
> +		 */
> +		root->inh_target_child_rels = lappend(root->inh_target_child_rels,
> +											  childrel);
> +	}
> +}

You're right.  Checking that in inheritance_adjust_scanjoin_target was
redundant.

> 3.
> Is the "root->parse->commandType != CMD_INSERT" required in:
> 
> @@ -2018,13 +1514,45 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
> ...
> +		/*
> +		 * For UPDATE/DELETE on an inheritance parent, we must generate and
> +		 * apply scanjoin target based on targetlist computed using each
> +		 * of the child relations.
> +		 */
> +		if (parse->commandType != CMD_INSERT &&
> +			current_rel->reloptkind == RELOPT_BASEREL &&
> +			current_rel->relid == root->parse->resultRelation &&
> +			root->simple_rte_array[current_rel->relid]->inh)
> ...
> 
> @@ -2137,92 +1665,123 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
>  	final_rel->fdwroutine = current_rel->fdwroutine;
>  
> ...
> -	foreach(lc, current_rel->pathlist)
> +	if (current_rel->reloptkind == RELOPT_BASEREL &&
> +		current_rel->relid == root->parse->resultRelation &&
> +		!IS_DUMMY_REL(current_rel) &&
> +		root->simple_rte_array[current_rel->relid]->inh &&
> +		parse->commandType != CMD_INSERT)
> 
> 
> I think if a condition would be "current_rel->relid == root->parse->resultRelation && parse->commandType != CMD_INSERT" at the above if clause, elog() is called in the query_planner and planner don't reach above if clause.
> Of course there is the case that query_planner returns the dummy joinrel and elog is not called, but in that case, current_rel->relid may be 0(current_rel is dummy joinrel) and root->parse->resultRelation may be not 0(a query is INSERT).

Yeah, I realized that we can actually Assert(parse->commandType !=
CMD_INSERT) if the inh flag of the target/resultRelation RTE is true.  So,
checking it explicitly is redundant.

> 4. Can't we use define value(IS_PARTITIONED or something like IS_INHERITANCE?) to identify inheritance and partitioned table in below code? It was little confusing to me that which code is related to inheritance/partitioned when looking codes.
> 
> In make_one_rel():
> +	if (root->parse->resultRelation &&
> +		root->simple_rte_array[root->parse->resultRelation]->inh)
> +	{
> ...
> +		rel = inheritance_make_rel_from_joinlist(root, targetrel, joinlist);
> 
> In inheritance_make_rel_from_joinlist():
> +		if (childrel->part_scheme != NULL)
> +			childrel =
> +				inheritance_make_rel_from_joinlist(root, childrel,
> +												   translated_joinlist);

As it might have been clear, the value of inh flag is checked to detect
whether the operation uses inheritance, because it requires special
handling -- the operation must be applied to every child relation that
satisfies the conditions of the query.

part_scheme is checked to detect partitioning which has some special
behavior with respect to how the AppendRelInfos are set up.  For
partitioning, AppendRelInfos map partitions to their immediate parent, not
the top-most root parent, so the current code uses recursion to apply
certain transformations.  That's not required for non-partitioned case,
because all children are mapped to the root inheritance parent.

I'm trying to unify the two so that the partitioning case doesn't need any
special handling.

> I can't review inheritance_adjust_scanjoin_target() deeply, because it is difficult to me to understand fully codes about join processing.

Thanks for your comments, they are valuable.

Regards,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-10-26 10:37  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 2 replies; 265+ messages in thread

From: Amit Langote @ 2018-10-26 10:37 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: pgsql-hackers; David Rowley <david.rowley@2ndquadrant.com>

And here is the latest set of patches.  Sorry it took me a while.

* To reiterate, this set of patches is intended to delay adding partitions
to the query's range table and PlannerInfo, which leads to significantly
reduced overheads in the common case where queries accessing partitioned
tables need to scan only 1 or few partitions.  As I mentioned before on
this thread [1], I wanted to change the patches such that the new approach
is adopted for all inheritance tables, not just partitioned tables,
because most of the code and data structures are shared and it no longer
made sense to me to create a diversion between the two cases by making the
partitioning a special case in a number of places in the planner code.  I
think I've managed to get to that point with the latest patches.  The new
code that performs inheritance expansion is common to partitioning,
inheritance, and also flattened UNION ALL to some degree, because all of
these cases use more or less the same code.  For partitioning, child
relations are opened and RTEs are added for them *after* performing
pruning, whereas for inheritance that's not possible, because pruning
using constraint exclusion cannot be performed without opening the
children.  This consideration is the only main thing that differs between
the handling of the two case, among other minor details that also differ.

* With the unification described above, inheritance expansion is no longer
part planner's prep phase, so I've located the new expansion code in a new
file under optimizer/utils named append.c, because it manages creation of
an appendrel's child relations.  That means a lot of helper code that once
was in prepunion.c is now in the new file.  Actually, I've moved *some* of
the helper routines such as adjust_appendre_* that do the mapping of
expressions between appendrel parent and children to a different file
appendinfo.c.

* I've also rewritten the patch to change how inherited update/delete
planning is done, so that it doesn't end up messing too much with
grouping_planner.  Instead of removing inheritance_planner altogether as
the earlier patches did, I've rewritten it such that the CPU and memory
intensive query_planner portion is run only once at the beginning to
create scan/join paths for non-excluded/unpruned child target relations,
followed by running grouping_planner to apply top-level targetlist
suitable for each child target relation.  grouping_planner is modified
slightly such that when it runs for every child target relation, it
doesn't again need to perform query planning.  It instead receives the
RelOptInfos that contain the scan/join paths of child target relations
that were generated during the initial query_planner run.  Beside this
main change, I've removed quite a bit of code in inheritance_planner that
relied on the existing way of redoing planning for each child target
relation.  The changes are described in the commit message of patch 0002.

* A few notes on the patches:

0001 is a separate patch, because it might be useful in some other
projects like [2].

0003 is David's patch that he posted in [3].


I didn't get time today to repeat the performance tests, but I'm planning
to next week.  In the meantime, please feel free to test them and provide
comments on the code changes.

Thanks,
Amit

[1]
https://www.postgresql.org/message-id/8097576f-f795-fc42-3c00-073f68bfb0b4%40lab.ntt.co.jp

[2]
https://www.postgresql.org/message-id/de957e5b-faa0-6fb9-c0ab-0b389d71cf5a%40lab.ntt.co.jp

[3] Re: Calculate total_table_pages after set_base_rel_sizes()
https://www.postgresql.org/message-id/CAKJS1f9NiQXO9KCv_cGgBShwqwT78wmArOht-5kWL%2BBt0v-AnQ%40mail.g...

Attachments: download all (6 files, .tar.gz)

0001-Store-inheritance-root-parent-index-in-otherrel-s-Re.patch (text/plain, 2.5K) download From 59d57e19ee99bf63c4a4663738eae591da911d67 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 26 Oct 2018 16:45:59 +0900 Subject: [PATCH 1/6] Store inheritance root parent index in otherrel's RelOptInfo Although it's set by build_simple_rel, it's not being used by any code yet. --- src/backend/nodes/outfuncs.c | 1 + src/backend/optimizer/util/relnode.c | 14 ++++++++++++++ src/include/nodes/relation.h | 4 ++++ 3 files changed, 19 insertions(+) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 69731ccdea..53a657c0ae 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2371,6 +2371,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_BOOL_FIELD(consider_partitionwise_join); WRITE_BITMAPSET_FIELD(top_parent_relids); WRITE_NODE_FIELD(partitioned_child_rels); + WRITE_UINT_FIELD(inh_root_parent); } static void diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 39f5729b91..29ba19349f 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -215,9 +215,23 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->top_parent_relids = parent->top_parent_relids; else rel->top_parent_relids = bms_copy(parent->relids); + + /* + * For inheritance child relations, we also set inh_root_parent. + * Note that 'parent' might itself be a child (a sub-partitioned + * partition), in which case we simply use its value of + * inh_root_parent. + */ + if (parent->rtekind == RTE_RELATION) + rel->inh_root_parent = parent->inh_root_parent > 0 ? + parent->inh_root_parent : + parent->relid; } else + { rel->top_parent_relids = NULL; + rel->inh_root_parent = 0; + } /* Check type of rtable entry */ switch (rte->rtekind) diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 88d37236f7..bbc1408d05 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -703,6 +703,10 @@ typedef struct RelOptInfo List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ + + Index inh_root_parent; /* For otherrels, this is the RT index of + * inheritance table mentioned in the query + * from which this relation originated */ } RelOptInfo; /* -- 2.11.0
0002-Overhaul-inheritance-update-delete-planning.patch (text/plain, 44.9K) download From 1dbca70ccf6ba8d94c5d4b9b52bc2bde2078d695 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 11:18:12 +0900 Subject: [PATCH 2/6] Overhaul inheritance update/delete planning With the current method, inheritance_planner, which handles the planning for update/delete commands targeting inheritance trees, performs the *whole* planning for each child table. That involves translating the query tree to set a given child table in place of the original inheritance root table as the query's target table, followed by calling grouping_planner on the translated query. That's inefficient for two reasons: 1. partprune.c cannot be used to perform partition pruning, because it can only be invoked from query_planner. With the current approach, query_planner only sees the individual partitions in the query tree it receives, not the parent partitioned table. This leaves each partition to be pruned using constraint exclusion. 2. Repeated invocation of query_planner results in allocating large amounts of memory, especially if there are many child tables. Also, it involves repeatedly performing the same processing, such jointree processing. This commit addresses both of the issues by rewriting portions of inheritance_planner and adding some supporting functionality to allpath.c. With the new implementation, inheritance_planner calls query_planner only once at the beginning with the original unmodified query, which creates the access paths of individual relations after pruning any unnecessary partitions. If the query involves join against the target relation, join paths are created for each target child relation by replacing the original target table in the join tree by a given child table. Join relations (RelOptInfos thereof) for all target child relations are collected in a global list in PlannerInfo. After creating the join paths for all target child relations, it calls grouping_planner() on each child join relation to finish up the paths such that they produce query's top-level target list expanded per a given child relation's descriptor. grouping_planner()'s interface is modified so that we can pass it what's called a 'planned_rel', a RelOptInfo that already contains the necessary paths needed to produce its output. This removes some existing code in inheritance_planner that dealt with any subquery RTEs in the query. The rationale of that code was that the subquery RTEs may change during each iteration of planning (that is, for different children), so different iterations better use different copies of those RTEs. That was handled by making fresh copies of those RTEs for each iteration of planning which were appended to the range table, accompanied by modifying all expressions and auxiliary structures that referenced the original subquery RTEs to instead reference the copies (that is, change the varnos). This copying meant we would end up adding S * N new entries to the original range table by the time we got to the last unpruned child, where S is the number of subquery RTEs in the original query and N the number of unpruned children. Since with the new code we perform planning just once, I think we don't need this special handling. Actually, there is a regression test output change due to no longer having copies of subquery RTEs (see the diff of partition_join.out file.) Also we no longer need the hack in relation_excluded_by_constraints() involving setting PlannerInfo.inhTargetKind to enable constraint exclusion for target child relation is no longer needed. This commit removes inheritance_planner in favor of rearranging things such that, only the planning steps that affect 1 and 2 above are repeated for unpruned child relations. --- doc/src/sgml/ddl.sgml | 14 +- src/backend/nodes/outfuncs.c | 1 - src/backend/optimizer/path/allpaths.c | 212 +++++++++++++- src/backend/optimizer/plan/createplan.c | 10 - src/backend/optimizer/plan/planner.c | 400 +++++++-------------------- src/backend/optimizer/prep/prepjointree.c | 1 - src/backend/optimizer/prep/prepunion.c | 33 ++- src/backend/optimizer/util/plancat.c | 50 +--- src/include/nodes/relation.h | 18 +- src/test/regress/expected/partition_join.out | 4 +- 10 files changed, 356 insertions(+), 387 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index b5ed1b7939..8cb69b1a47 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -3935,15 +3935,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <note> <para> - Currently, pruning of partitions during the planning of an - <command>UPDATE</command> or <command>DELETE</command> command is - implemented using the constraint exclusion method (however, it is - controlled by the <literal>enable_partition_pruning</literal> rather than - <literal>constraint_exclusion</literal>) &mdash; see the following section - for details and caveats that apply. - </para> - - <para> Execution-time partition pruning currently occurs for the <literal>Append</literal> and <literal>MergeAppend</literal> node types. </para> @@ -3964,9 +3955,8 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <para> <firstterm>Constraint exclusion</firstterm> is a query optimization - technique similar to partition pruning. While it is primarily used - for partitioning implemented using the legacy inheritance method, it can be - used for other purposes, including with declarative partitioning. + technique similar to partition pruning. It is primarily used + for partitioning implemented using the legacy inheritance method. </para> <para> diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 53a657c0ae..a95153fea7 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2309,7 +2309,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); WRITE_UINT_FIELD(qual_security_level); - WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind); WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasDeletedRTEs); diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 5f74d3b36d..aa678eac82 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -36,6 +36,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" @@ -119,6 +120,9 @@ static void set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); +static void inheritance_make_rel_from_joinlist(PlannerInfo *root, + RelOptInfo *parent, + List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, pushdown_safety_info *safetyInfo); static bool recurse_pushdown_safe(Node *setOp, Query *topquery, @@ -181,13 +185,28 @@ make_one_rel(PlannerInfo *root, List *joinlist) /* * Generate access paths for the entire join tree. + * + * For UPDATE/DELETE on an inheritance parent, join paths should be + * generated for each child result rel separately. */ - rel = make_rel_from_joinlist(root, joinlist); + if (root->parse->resultRelation && + root->simple_rte_array[root->parse->resultRelation]->inh) + { + rel = root->simple_rel_array[root->parse->resultRelation]; + inheritance_make_rel_from_joinlist(root, rel, joinlist); + Assert(list_length(root->inh_target_child_rels) == + list_length(root->inh_target_child_joinrels)); + } + else + { + rel = make_rel_from_joinlist(root, joinlist); - /* - * The result should join all and only the query's base rels. - */ - Assert(bms_equal(rel->relids, root->all_baserels)); + /* + * The result should join all and only the query's base rels. + */ + Assert(bms_equal(rel->relids, root->all_baserels)); + + } return rel; } @@ -1183,6 +1202,63 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, set_rel_consider_parallel(root, childrel, childRTE); /* + * If the root parent is the result relation, we need a reltarget + * for the child that will be suitable to use the child as + * target relation. + */ + if (childrel->inh_root_parent > 0 && + childrel->inh_root_parent == root->parse->resultRelation) + { + Query *parse, + *orig_parse = root->parse; + List *tlist; + List *other_exprs; + ListCell *lc2; + Relids child_relids, + top_parent_relids; + + /* + * We'd like to build the reltarget afresh; save the translated + * version of parent's expressions aside. + */ + other_exprs = childrel->reltarget->exprs; + childrel->reltarget->exprs = NIL; + + /* + * Translate the original query's expressions to this child, + * mainly for updating the childrel's reltarget so that it + * contains necessary expressions for using childrel as target + * relation correctly. We need to use multilevel translation + * as we can only access the original version of the query. + */ + child_relids = bms_make_singleton(appinfo->child_relid); + top_parent_relids = bms_make_singleton(childrel->inh_root_parent); + parse = (Query *) + adjust_appendrel_attrs_multilevel(root, + (Node *) root->parse, + child_relids, + top_parent_relids); + root->parse = parse; + tlist = preprocess_targetlist(root); + build_base_rel_tlists(root, tlist); + root->parse = orig_parse; + + /* + * Some of the expressions in parent's reltarget might not be + * in child's just built expressions. Check to add any that are + * missing. + */ + foreach(lc2, other_exprs) + { + Expr *expr = lfirst(lc2); + + if (!list_member(childrel->reltarget->exprs, expr)) + childrel->reltarget->exprs = + lappend(childrel->reltarget->exprs, expr); + } + } + + /* * Compute the child's size. */ set_rel_size(root, childrel, childRTindex, childRTE); @@ -2583,6 +2659,132 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows) } /* + * inheritance_make_rel_from_joinlist + * Perform join planning for all non-dummy leaf inheritance chilren + * in their role as query's target relation + * + * If a child relation is a partitioned table, its children are processed in + * turn by recursively calling this function. + */ +static void +inheritance_make_rel_from_joinlist(PlannerInfo *root, + RelOptInfo *parent, + List *joinlist) +{ + List *join_info_list = root->join_info_list; + ListCell *lc; +#ifdef USE_ASSERT_CHECKING + int top_parent_relid = parent->inh_root_parent > 0 ? + parent->inh_root_parent : + parent->relid; + Relids all_baserels; +#endif + + /* + * For UPDATE/DELETE queries, the top parent can only ever be a table. + * As a contrast, it could be a UNION ALL subquery in the case of SELECT. + */ + Assert(planner_rt_fetch(top_parent_relid, root)->rtekind == RTE_RELATION); + + /* Nothing to do. */ + if (IS_DUMMY_REL(parent)) + return; + + foreach(lc, root->append_rel_list) + { + RelOptInfo *childrel; + AppendRelInfo *appinfo = lfirst(lc); + List *translated_joinlist; + Relids child_relids, + top_parent_relids; + + /* Only consider this parent's children. */ + if (appinfo->parent_relid != parent->relid) + continue; + + childrel = find_base_rel(root, appinfo->child_relid); + + /* Ignore excluded/pruned children. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* + * Sub-partitions have to be processed recursively, because + * AppendRelInfoss link sub-partitions to their immediate parents, not + * the root partitioned table. + */ + if (childrel->part_scheme != NULL) + { + inheritance_make_rel_from_joinlist(root, childrel, joinlist); + continue; + } + + /* + * Modify joinlist such that relations joined to the top parent rel + * appear to be joined to the child rel instead. Do the same for + * any SpecialJoinInfo structs. + */ + child_relids = bms_make_singleton(appinfo->child_relid); + Assert(childrel->inh_root_parent > 0); + top_parent_relids = bms_make_singleton(childrel->inh_root_parent); + translated_joinlist = (List *) + adjust_appendrel_attrs_multilevel(root, + (Node *) joinlist, + child_relids, + top_parent_relids); + root->join_info_list = (List *) + adjust_appendrel_attrs_multilevel(root, + (Node *) join_info_list, + child_relids, + top_parent_relids); + + /* + * Since we added the child rel directly into the join tree, we must + * modify it to be a "base" rel instead of an "other" rel, which the + * join planning code expects the relations being joined to be. + * + * NB: Do we need to change the child EC members to be marked + * as non-child somehow? + */ + childrel->reloptkind = RELOPT_BASEREL; + + /* Reset interal join planning data structures. */ + root->join_rel_list = NIL; + root->join_rel_hash = NULL; + + /* Perform join planning and save the resulting RelOptInfo. */ + childrel = make_rel_from_joinlist(root, translated_joinlist); + + /* + * Remember this child target rel. inheritance_planner will perform + * the remaining steps of planning for each child relation separately. + * Specifically, it will call grouping_planner on every + * RelOptInfo contained in the inh_target_child_joinrels list, each + * of which represents the source of tuples to be modified for a given + * target child rel. + */ + root->inh_target_child_rels = lappend_int(root->inh_target_child_rels, + appinfo->child_relid); + root->inh_target_child_joinrels = + lappend(root->inh_target_child_joinrels, childrel); + +#ifdef USE_ASSERT_CHECKING + /* + * The following implements essentially the same Assert as in + * make_one_rel, our caller. + */ + all_baserels = bms_copy(root->all_baserels); + all_baserels = bms_del_member(all_baserels, top_parent_relid); + all_baserels = bms_add_member(all_baserels, appinfo->child_relid); + Assert(bms_equal(childrel->relids, all_baserels)); +#endif + + /* Restore the original for processing the next child. */ + root->join_info_list = join_info_list; + } +} + +/* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. * diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index ae46b0140e..08ee0cf7c6 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -1998,12 +1998,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) /* * During setrefs.c, we'll need the grouping_map to fix up the cols lists * in GroupingFunc nodes. Save it for setrefs.c to use. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be grouping in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->grouping_map == NULL); root->grouping_map = grouping_map; @@ -2160,12 +2155,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) * with InitPlan output params. (We can't just do that locally in the * MinMaxAgg node, because path nodes above here may have Agg references * as well.) Save the mmaggregates list to tell setrefs.c to do that. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be aggregates in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->minmax_aggs == NIL); root->minmax_aggs = best_path->mmaggregates; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index c729a99f8b..93b8c761c2 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -37,6 +37,7 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "nodes/relation.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" @@ -126,7 +127,7 @@ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static void inheritance_planner(PlannerInfo *root); static void grouping_planner(PlannerInfo *root, bool inheritance_update, - double tuple_fraction); + RelOptInfo *planned_rel, double tuple_fraction); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, int *tleref_to_colnum_map); @@ -630,7 +631,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->grouping_map = NULL; root->minmax_aggs = NIL; root->qual_security_level = 0; - root->inhTargetKind = INHKIND_NONE; root->hasRecursion = hasRecursion; if (hasRecursion) root->wt_param_id = SS_assign_special_param(root); @@ -970,7 +970,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh) inheritance_planner(root); else - grouping_planner(root, false, tuple_fraction); + grouping_planner(root, false, NULL, tuple_fraction); /* * Capture the set of outer-level param IDs we have access to, for use in @@ -1146,12 +1146,19 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr) * inheritance set. * * We have to handle this case differently from cases where a source relation - * is an inheritance set. Source inheritance is expanded at the bottom of the - * plan tree (see allpaths.c), but target inheritance has to be expanded at - * the top. The reason is that for UPDATE, each target relation needs a - * different targetlist matching its own column set. Fortunately, - * the UPDATE/DELETE target can never be the nullable side of an outer join, - * so it's OK to generate the plan this way. + * is an inheritance set. That's mainly because for UPDATE, each target + * relation needs a different targetlist matching its own column set. So, we + * must modify the source scan/join path for each target relation such that it + * produces the desired target list. + * + * The source scan/join paths for individual target relations are still + * created in allpaths.c by first expanding the inheritance in the usual way + * by set_append_rel_size, followed by join planning for each target relation + * separately in make_one_rel. Finally, we apply grouping_planner here to each + * child scan/join path so that it produces the desired targetlist. + * + * Fortunately, the UPDATE/DELETE target can never be the nullable side of an + * outer join, so it's OK to generate the plan this way. * * Returns nothing; the useful output is in the Paths we attach to * the (UPPERREL_FINAL, NULL) upperrel stored in *root. @@ -1164,14 +1171,8 @@ inheritance_planner(PlannerInfo *root) { Query *parse = root->parse; int top_parentRTindex = parse->resultRelation; - Bitmapset *subqueryRTindexes; - Bitmapset *modifiableARIindexes; int nominalRelation = -1; Index rootRelation = 0; - List *final_rtable = NIL; - int save_rel_array_size = 0; - RelOptInfo **save_rel_array = NULL; - AppendRelInfo **save_append_rel_array = NULL; List *subpaths = NIL; List *subroots = NIL; List *resultRelations = NIL; @@ -1179,70 +1180,60 @@ inheritance_planner(PlannerInfo *root) List *returningLists = NIL; List *rowMarks; RelOptInfo *final_rel; - ListCell *lc; - Index rti; + ListCell *lc1, + *lc2; RangeTblEntry *parent_rte; - PlannerInfo *parent_root; - Query *parent_parse; - Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); - PlannerInfo **parent_roots = NULL; + List *tlist, + *orig_tlist; + standard_qp_extra qp_extra; + RelOptInfo *planned_rel; + List *partitioned_rels = NIL; + /* Inheritance is never used for insert. */ Assert(parse->commandType != CMD_INSERT); + parent_rte = planner_rt_fetch(top_parentRTindex, root); /* - * We generate a modified instance of the original Query for each target - * relation, plan that, and put all the plans into a list that will be - * controlled by a single ModifyTable node. All the instances share the - * same rangetable, but each instance must have its own set of subquery - * RTEs within the finished rangetable because (1) they are likely to get - * scribbled on during planning, and (2) it's not inconceivable that - * subqueries could get planned differently in different cases. We need - * not create duplicate copies of other RTE kinds, in particular not the - * target relations, because they don't have either of those issues. Not - * having to duplicate the target relations is important because doing so - * (1) would result in a rangetable of length O(N^2) for N targets, with - * at least O(N^3) work expended here; and (2) would greatly complicate - * management of the rowMarks list. + * Generate the access paths for all relations mentioned in the query, + * including the target inheritance set. When doing the join planning, + * references in the join tree to the original target relation that's the + * root parent of the inheritance tree is replaced by each of its + * inheritance children and the resulting joinrel RelOptInfo's are + * added to root->inh_target_child_joinrels. * - * To begin with, generate a bitmapset of the relids of the subquery RTEs. + * Final planning steps (grouping_planner) are applied to the best path + * of each of those child joinrels using a modified instance of the + * original query for a given child target rel. All the paths so generated + * are put into a list that will be controlled by a single ModifyTable + * node. All the instances share the same rangetable. */ - subqueryRTindexes = NULL; - rti = 1; - foreach(lc, parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - if (rte->rtekind == RTE_SUBQUERY) - subqueryRTindexes = bms_add_member(subqueryRTindexes, rti); - rti++; - } /* - * Next, we want to identify which AppendRelInfo items contain references - * to any of the aforesaid subquery RTEs. These items will need to be - * copied and modified to adjust their subquery references; whereas the - * other ones need not be touched. It's worth being tense over this - * because we can usually avoid processing most of the AppendRelInfo - * items, thereby saving O(N^2) space and time when the target is a large - * inheritance tree. We can identify AppendRelInfo items by their - * child_relid, since that should be unique within the list. + * Save the unmodified version of the query's targetlist to be used below + * for passing to grouping_planner for each child target relation. */ - modifiableARIindexes = NULL; - if (subqueryRTindexes != NULL) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); + orig_tlist = list_copy(root->parse->targetList); - if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) || - bms_is_member(appinfo->child_relid, subqueryRTindexes) || - bms_overlap(pull_varnos((Node *) appinfo->translated_vars), - subqueryRTindexes)) - modifiableARIindexes = bms_add_member(modifiableARIindexes, - appinfo->child_relid); - } + /* Do the scan/join planning. */ + tlist = preprocess_targetlist(root); + qp_extra.tlist = tlist; + qp_extra.activeWindows = qp_extra.groupClause = NIL; + planned_rel = query_planner(root, tlist, standard_qp_callback, &qp_extra); + + /* + * If it turned out during query planning that all the children are dummy + * (pruned or excluded by constraints), no need to do the steps below. + * Let grouping_planner finish up the final path. + */ + if (IS_DUMMY_REL(planned_rel)) + { + grouping_planner(root, false, planned_rel, 0.0); + return; } + Assert(planned_rel->relid == top_parentRTindex); + Assert(planned_rel->reloptkind == RELOPT_BASEREL); + /* * If the parent RTE is a partitioned table, we should use that as the * nominal target relation, because the RTEs added for partitioned tables @@ -1250,67 +1241,50 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; rootRelation = top_parentRTindex; + partitioned_rels = lappend(partitioned_rels, + list_make1_int(top_parentRTindex)); } /* - * The PlannerInfo for each child is obtained by translating the relevant - * members of the PlannerInfo for its immediate parent, which we find - * using the parent_relid in its AppendRelInfo. We save the PlannerInfo - * for each parent in an array indexed by relid for fast retrieval. Since - * the maximum number of parents is limited by the number of RTEs in the - * query, we use that number to allocate the array. An extra entry is - * needed since relids start from 1. + * Get on with finalizing the path for each child target relation by + * calling grouping_planner on its joinrel. Note that we're restoring + * the query's targetlist to the original one for grouping_planner's + * targetlist expansion steps to perform the expansion with individual + * child descriptors. */ - parent_roots = (PlannerInfo **) palloc0((list_length(parse->rtable) + 1) * - sizeof(PlannerInfo *)); - parent_roots[top_parentRTindex] = root; - - /* - * And now we can get on with generating a plan for each child table. - */ - foreach(lc, root->append_rel_list) + parse->targetList = orig_tlist; + forboth(lc1, root->inh_target_child_rels, + lc2, root->inh_target_child_joinrels) { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); + AppendRelInfo *appinfo = root->append_rel_array[lfirst_int(lc1)]; + RelOptInfo *child_joinrel = lfirst(lc2); PlannerInfo *subroot; RangeTblEntry *child_rte; RelOptInfo *sub_final_rel; Path *subpath; - - /* append_rel_list contains all append rels; ignore others */ - if (!bms_is_member(appinfo->parent_relid, parent_relids)) - continue; - - /* - * expand_inherited_rtentry() always processes a parent before any of - * that parent's children, so the parent_root for this relation should - * already be available. - */ - parent_root = parent_roots[appinfo->parent_relid]; - Assert(parent_root != NULL); - parent_parse = parent_root->parse; + Relids child_relids = bms_make_singleton(appinfo->child_relid), + top_parent_relids = bms_make_singleton(top_parentRTindex); /* * We need a working copy of the PlannerInfo so that we can control * propagation of information back to the main copy. */ subroot = makeNode(PlannerInfo); - memcpy(subroot, parent_root, sizeof(PlannerInfo)); + memcpy(subroot, root, sizeof(PlannerInfo)); /* * Generate modified query with this rel as target. We first apply * adjust_appendrel_attrs, which copies the Query and changes - * references to the parent RTE to refer to the current child RTE, - * then fool around with subquery RTEs. + * references to the parent RTE to refer to the current child RTE. */ subroot->parse = (Query *) - adjust_appendrel_attrs(parent_root, - (Node *) parent_parse, - 1, &appinfo); + adjust_appendrel_attrs_multilevel(root, (Node *) parse, + child_relids, + top_parent_relids); /* * If there are securityQuals attached to the parent, move them to the @@ -1322,33 +1296,10 @@ inheritance_planner(PlannerInfo *root) parent_rte->securityQuals = NIL; /* - * Mark whether we're planning a query to a partitioned table or an - * inheritance parent. + * inheritance_make_rel_from_joinlist only adds the leaf partitions to + * PlannerInfo. */ - subroot->inhTargetKind = - (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED; - - /* - * If this child is further partitioned, remember it as a parent. - * Since a partitioned table does not have any data, we don't need to - * create a plan for it, and we can stop processing it here. We do, - * however, need to remember its modified PlannerInfo for use when - * processing its children, since we'll update their varnos based on - * the delta from immediate parent to child, not from top to child. - * - * Note: a very non-obvious point is that we have not yet added - * duplicate subquery RTEs to the subroot's rtable. We mustn't, - * because then its children would have two sets of duplicates, - * confusing matters. - */ - if (child_rte->inh) - { - Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE); - parent_relids = bms_add_member(parent_relids, appinfo->child_relid); - parent_roots[appinfo->child_relid] = subroot; - - continue; - } + Assert(!child_rte->inh); /* * Set the nominal target relation of the ModifyTable node if not @@ -1374,111 +1325,8 @@ inheritance_planner(PlannerInfo *root) if (nominalRelation < 0) nominalRelation = appinfo->child_relid; - /* - * The rowMarks list might contain references to subquery RTEs, so - * make a copy that we can apply ChangeVarNodes to. (Fortunately, the - * executor doesn't need to see the modified copies --- we can just - * pass it the original rowMarks list.) - */ - subroot->rowMarks = copyObject(parent_root->rowMarks); - - /* - * The append_rel_list likewise might contain references to subquery - * RTEs (if any subqueries were flattenable UNION ALLs). So prepare - * to apply ChangeVarNodes to that, too. As explained above, we only - * want to copy items that actually contain such references; the rest - * can just get linked into the subroot's append_rel_list. - * - * If we know there are no such references, we can just use the outer - * append_rel_list unmodified. - */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - subroot->append_rel_list = NIL; - foreach(lc2, parent_root->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, modifiableARIindexes)) - appinfo2 = copyObject(appinfo2); - - subroot->append_rel_list = lappend(subroot->append_rel_list, - appinfo2); - } - } - - /* - * Add placeholders to the child Query's rangetable list to fill the - * RT indexes already reserved for subqueries in previous children. - * These won't be referenced, so there's no need to make them very - * valid-looking. - */ - while (list_length(subroot->parse->rtable) < list_length(final_rtable)) - subroot->parse->rtable = lappend(subroot->parse->rtable, - makeNode(RangeTblEntry)); - - /* - * If this isn't the first child Query, generate duplicates of all - * subquery RTEs, and adjust Var numbering to reference the - * duplicates. To simplify the loop logic, we scan the original rtable - * not the copy just made by adjust_appendrel_attrs; that should be OK - * since subquery RTEs couldn't contain any references to the target - * rel. - */ - if (final_rtable != NIL && subqueryRTindexes != NULL) - { - ListCell *lr; - - rti = 1; - foreach(lr, parent_parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lr); - - if (bms_is_member(rti, subqueryRTindexes)) - { - Index newrti; - - /* - * The RTE can't contain any references to its own RT - * index, except in its securityQuals, so we can save a - * few cycles by applying ChangeVarNodes to the rest of - * the rangetable before we append the RTE to it. - */ - newrti = list_length(subroot->parse->rtable) + 1; - ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); - ChangeVarNodes((Node *) subroot->rowMarks, rti, newrti, 0); - /* Skip processing unchanging parts of append_rel_list */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - foreach(lc2, subroot->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, - modifiableARIindexes)) - ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); - } - } - rte = copyObject(rte); - ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); - subroot->parse->rtable = lappend(subroot->parse->rtable, - rte); - } - rti++; - } - } - - /* There shouldn't be any OJ info to translate, as yet */ - Assert(subroot->join_info_list == NIL); - /* and we haven't created PlaceHolderInfos, either */ - Assert(subroot->placeholder_list == NIL); - - /* Generate Path(s) for accessing this result relation */ - grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ ); + /* Apply the top-level targetlist to child_joinrel's path. */ + grouping_planner(subroot, true, child_joinrel, 0.0); /* * Select cheapest path in case there's more than one. We always run @@ -1490,45 +1338,10 @@ inheritance_planner(PlannerInfo *root) subpath = sub_final_rel->cheapest_total_path; /* - * If this child rel was excluded by constraint exclusion, exclude it - * from the result plan. + * child rel cannot be empty, or inheritance_make_rel_from_joinlist + * wouldn't have put it in the list to begin with. */ - if (IS_DUMMY_PATH(subpath)) - continue; - - /* - * If this is the first non-excluded child, its post-planning rtable - * becomes the initial contents of final_rtable; otherwise, append - * just its modified subquery RTEs to final_rtable. - */ - if (final_rtable == NIL) - final_rtable = subroot->parse->rtable; - else - final_rtable = list_concat(final_rtable, - list_copy_tail(subroot->parse->rtable, - list_length(final_rtable))); - - /* - * We need to collect all the RelOptInfos from all child plans into - * the main PlannerInfo, since setrefs.c will need them. We use the - * last child's simple_rel_array (previous ones are too short), so we - * have to propagate forward the RelOptInfos that were already built - * in previous children. - */ - Assert(subroot->simple_rel_array_size >= save_rel_array_size); - for (rti = 1; rti < save_rel_array_size; rti++) - { - RelOptInfo *brel = save_rel_array[rti]; - - if (brel) - subroot->simple_rel_array[rti] = brel; - } - save_rel_array_size = subroot->simple_rel_array_size; - save_rel_array = subroot->simple_rel_array; - save_append_rel_array = subroot->append_rel_array; - - /* Make sure any initplans from this rel get into the outer list */ - root->init_plans = subroot->init_plans; + Assert(!IS_DUMMY_PATH(subpath)); /* Build list of sub-paths */ subpaths = lappend(subpaths, subpath); @@ -1560,36 +1373,6 @@ inheritance_planner(PlannerInfo *root) */ /* - * If we managed to exclude every child rel, return a dummy plan; it - * doesn't even need a ModifyTable node. - */ - if (subpaths == NIL) - { - set_dummy_rel_pathlist(final_rel); - return; - } - - /* - * Put back the final adjusted rtable into the master copy of the Query. - * (We mustn't do this if we found no non-excluded children.) - */ - parse->rtable = final_rtable; - root->simple_rel_array_size = save_rel_array_size; - root->simple_rel_array = save_rel_array; - root->append_rel_array = save_append_rel_array; - - /* Must reconstruct master's simple_rte_array, too */ - root->simple_rte_array = (RangeTblEntry **) - palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *)); - rti = 1; - foreach(lc, final_rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - root->simple_rte_array[rti++] = rte; - } - - /* * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will * have dealt with fetching non-locked marked rows, else we need to have * ModifyTable do that. @@ -1629,6 +1412,12 @@ inheritance_planner(PlannerInfo *root) * (inheritance_planner will create a single ModifyTable node covering all the * target tables.) * + * If non-NULL, planned_rel is a RelOptInfo containing paths for the query's + * top-level joinrel, which the caller produced by itself. In that case, this + * function only needs to adjust the targetlist of its cheapest_total_path. + * The only caller that may pass such a RelOptInfo currently is + * inheritance_planner. + * * tuple_fraction is the fraction of tuples we expect will be retrieved. * tuple_fraction is interpreted as follows: * 0: expect all tuples to be retrieved (normal case) @@ -1647,6 +1436,7 @@ inheritance_planner(PlannerInfo *root) */ static void grouping_planner(PlannerInfo *root, bool inheritance_update, + RelOptInfo *planned_rel, double tuple_fraction) { Query *parse = root->parse; @@ -1659,7 +1449,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, List *final_targets; List *final_targets_contain_srfs; bool final_target_parallel_safe; - RelOptInfo *current_rel; + RelOptInfo *current_rel = planned_rel; RelOptInfo *final_rel; ListCell *lc; @@ -1699,6 +1489,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * special work for recursive unions is the responsibility of * plan_set_operations. */ + Assert(current_rel == NULL); current_rel = plan_set_operations(root); /* @@ -1878,8 +1669,9 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * We also generate (in standard_qp_callback) pathkey representations * of the query's sort clause, distinct clause, etc. */ - current_rel = query_planner(root, tlist, - standard_qp_callback, &qp_extra); + if (current_rel == NULL) + current_rel = query_planner(root, tlist, + standard_qp_callback, &qp_extra); /* * Convert the query's result tlist into PathTarget format. diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index cd6e11904e..78baec00dc 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -917,7 +917,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->grouping_map = NULL; subroot->minmax_aggs = NIL; subroot->qual_security_level = 0; - subroot->inhTargetKind = INHKIND_NONE; subroot->hasRecursion = false; subroot->wt_param_id = -1; subroot->non_recursive_path = NULL; diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index d5720518a8..ef1f978889 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -2253,8 +2253,39 @@ adjust_appendrel_attrs_mutator(Node *node, context->appinfos); return (Node *) phv; } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 46de00460d..e7b4965337 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1265,36 +1265,6 @@ get_relation_constraints(PlannerInfo *root, } } - /* - * Append partition predicates, if any. - * - * For selects, partition pruning uses the parent table's partition bound - * descriptor, instead of constraint exclusion which is driven by the - * individual partition's partition constraint. - */ - if (enable_partition_pruning && root->parse->commandType != CMD_SELECT) - { - List *pcqual = RelationGetPartitionQual(relation); - - if (pcqual) - { - /* - * Run the partition quals through const-simplification similar to - * check constraints. We skip canonicalize_qual, though, because - * partition quals should be in canonical form already; also, - * since the qual is in implicit-AND format, we'd have to - * explicitly convert it to explicit-AND format and back again. - */ - pcqual = (List *) eval_const_expressions(root, (Node *) pcqual); - - /* Fix Vars to have the desired varno */ - if (varno != 1) - ChangeVarNodes((Node *) pcqual, 1, varno, 0); - - result = list_concat(result, pcqual); - } - } - heap_close(relation, NoLock); return result; @@ -1421,31 +1391,15 @@ relation_excluded_by_constraints(PlannerInfo *root, switch (constraint_exclusion) { case CONSTRAINT_EXCLUSION_OFF: - - /* - * Don't prune if feature turned off -- except if the relation is - * a partition. While partprune.c-style partition pruning is not - * yet in use for all cases (update/delete is not handled), it - * would be a UI horror to use different user-visible controls - * depending on such a volatile implementation detail. Therefore, - * for partitioned tables we use enable_partition_pruning to - * control this behavior. - */ - if (root->inhTargetKind == INHKIND_PARTITIONED) - break; return false; case CONSTRAINT_EXCLUSION_PARTITION: /* * When constraint_exclusion is set to 'partition' we only handle - * OTHER_MEMBER_RELs, or BASERELs in cases where the result target - * is an inheritance parent or a partitioned table. + * OTHER_MEMBER_RELs. */ - if ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) && - !(rel->reloptkind == RELOPT_BASEREL && - root->inhTargetKind != INHKIND_NONE && - rel->relid == root->parse->resultRelation)) + if (rel->reloptkind != RELOPT_OTHER_MEMBER_REL) return false; break; diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index bbc1408d05..21f1e4e6e6 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -318,9 +318,6 @@ typedef struct PlannerInfo Index qual_security_level; /* minimum security_level for quals */ /* Note: qual_security_level is zero if there are no securityQuals */ - InheritanceKind inhTargetKind; /* indicates if the target relation is an - * inheritance child or partition or a - * partitioned table */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */ bool hasDeletedRTEs; /* true if any RTE was deleted from jointree */ @@ -342,6 +339,21 @@ typedef struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* + * The following fields are set during query planning portion of an + * inherited UPDATE/DEELETE operation. + */ + + /* RT indexes of child target rels */ + List *inh_target_child_rels; + + /* + * RelOptInfos representing the output of make_rel_from_joinlist() for + * a each child rel in the above list, acting as the target relation in + * place of the original parent target relation. + */ + List *inh_target_child_joinrels; } PlannerInfo; diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index 3ba3aaf2d8..a539280851 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1781,7 +1781,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_1 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_1 -> Nested Loop Semi Join @@ -1789,7 +1789,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_2 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_2 (28 rows) -- 2.11.0
0003-Calculate-total_table_pages-after-set_base_rel_sizes.patch (text/plain, 4.8K) download From 1339a8c782f941d213300f6bb7067a79956b3280 Mon Sep 17 00:00:00 2001 From: "dgrowley@gmail.com" <dgrowley@gmail.com> Date: Mon, 6 Aug 2018 19:50:11 +1200 Subject: [PATCH 3/6] Calculate total_table_pages after set_base_rel_sizes Previously we did this before we attempted to prune partitions or eliminate append children with constraint exclusion. This could lead to incorrectly applying random_page_cost to more pages in an index scan, which could result in other scan types being preferred incorrectly. Author: David Rowley --- src/backend/optimizer/path/allpaths.c | 39 +++++++++++++++++++++++++++++++++-- src/backend/optimizer/plan/planmain.c | 30 --------------------------- src/include/nodes/relation.h | 3 ++- 3 files changed, 39 insertions(+), 33 deletions(-) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index aa678eac82..051e4a8e74 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -151,6 +151,7 @@ make_one_rel(PlannerInfo *root, List *joinlist) { RelOptInfo *rel; Index rti; + double total_pages; /* * Construct the all_baserels Relids set. @@ -177,10 +178,44 @@ make_one_rel(PlannerInfo *root, List *joinlist) set_base_rel_consider_startup(root); /* - * Compute size estimates and consider_parallel flags for each base rel, - * then generate access paths. + * Compute size estimates and consider_parallel flags for each base rel. */ set_base_rel_sizes(root); + + /* + * We should now have size estimates for every actual table involved in + * the query, and we also know which if any have been deleted from the + * query by join removal, pruned by partition pruning, and eliminated by + * constraint exclusion. We can now compute total_table_pages. + * + * Note that appendrels are not double-counted here, even though we don't + * bother to distinguish RelOptInfos for appendrel parents, because the + * parents will still have size zero. + * + * XXX if a table is self-joined, we will count it once per appearance, + * which perhaps is the wrong thing ... but that's not completely clear, + * and detecting self-joins here is difficult, so ignore it for now. + */ + total_pages = 0; + for (rti = 1; rti < root->simple_rel_array_size; rti++) + { + RelOptInfo *rel = root->simple_rel_array[rti]; + + if (rel == NULL) + continue; + + Assert(rel->relid == rti); /* sanity check on array */ + + if (IS_DUMMY_REL(rel)) + continue; + + if (IS_SIMPLE_REL(rel)) + total_pages += (double) rel->pages; + } + + root->total_table_pages = total_pages; + + /* Generate access paths. */ set_base_rel_pathlists(root); /* diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index b05adc70c4..9b6cc9e10f 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -57,8 +57,6 @@ query_planner(PlannerInfo *root, List *tlist, Query *parse = root->parse; List *joinlist; RelOptInfo *final_rel; - Index rti; - double total_pages; /* * If the query has an empty join tree, then it's something easy like @@ -232,34 +230,6 @@ query_planner(PlannerInfo *root, List *tlist, extract_restriction_or_clauses(root); /* - * We should now have size estimates for every actual table involved in - * the query, and we also know which if any have been deleted from the - * query by join removal; so we can compute total_table_pages. - * - * Note that appendrels are not double-counted here, even though we don't - * bother to distinguish RelOptInfos for appendrel parents, because the - * parents will still have size zero. - * - * XXX if a table is self-joined, we will count it once per appearance, - * which perhaps is the wrong thing ... but that's not completely clear, - * and detecting self-joins here is difficult, so ignore it for now. - */ - total_pages = 0; - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - - if (brel == NULL) - continue; - - Assert(brel->relid == rti); /* sanity check on array */ - - if (IS_SIMPLE_REL(brel)) - total_pages += (double) brel->pages; - } - root->total_table_pages = total_pages; - - /* * Ready to do the primary planning. */ final_rel = make_one_rel(root, joinlist); diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 21f1e4e6e6..06212ce382 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -310,7 +310,8 @@ typedef struct PlannerInfo MemoryContext planner_cxt; /* context holding PlannerInfo */ - double total_table_pages; /* # of pages in all tables of query */ + double total_table_pages; /* # of pages in all non-dummy tables of + * query */ double tuple_fraction; /* tuple_fraction passed to query_planner */ double limit_tuples; /* limit_tuples passed to query_planner */ -- 2.11.0
0004-Lazy-creation-of-RTEs-for-inheritance-children.patch (text/plain, 140.7K) download From 0454ee1969ac49ef056e9fc8896332c2cada54ef Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 18:14:13 +0900 Subject: [PATCH 4/6] Lazy creation of RTEs for inheritance children Currently, they're created in the planner's prep's phase, at which point, it's too early to perform partition pruning or constraint exclusion, which results in *all* partitions being opened and range table entries being created for them. With large number of children, that can be pretty inefficient overall if most queries would only access one or few of them. This commit moves the processing that's currently done in expand_inherited_table called from subquery_planner to a new function that's called from set_append_rel_size, which is late enough during query planning that we're able to perform partition pruning (and constraint exclusion). This also enables us to open the relation and initialize planner objects for only the unpruned partitions. For non-partition children, we still need to open them before we could perform constraint exclusion. Although the late initialization approach described above only benefits the partitioning case as far as the number of child relations that need to be opened is concerned, it seems undesirable to make a special case of it. Instead, this moves the inheritance expansion code into a new module named append.c whose functionality is invoked from set_append_rel_size, which contains the new code for inheritance child initialization. The only way partitioning is special in the new module is that it performs pruning before any child relations are initialized and only the unpruned children are initialized. A related new module named appendinfo.c contains the code related to inheritance translation of expressions. For partitioning, although we don't create planner data structures for pruned partitions, partitionwise join code relies on the fact fact that even though partitions may have been pruned, they'd still own a RelOptInfo to handle the outer join case where the pruned partition appears on the nullable side of join. Partitionwise join code deals with that by allocating dummy RelOptInfos for pruned partitions. There are some regression test diffs: 1. Caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child and also that the individual partition RT entries are now created in the order in which their parent's are processed, whereas previously they'd be added to the range table in the order of depth-first expansion of inheritance tree. 2. Those in postgres_fdw.out are caused by the fact that junk columns required for row marking are added to reltarget->exprs later than user columns, because the row marking junk columns arent added until after the inheritance is expanded which as of this commit is later than it used to be. --- contrib/postgres_fdw/expected/postgres_fdw.out | 32 +- src/backend/optimizer/path/allpaths.c | 206 +--- src/backend/optimizer/path/equivclass.c | 1 + src/backend/optimizer/path/joinrels.c | 63 ++ src/backend/optimizer/plan/initsplan.c | 58 - src/backend/optimizer/plan/planner.c | 55 +- src/backend/optimizer/plan/setrefs.c | 6 + src/backend/optimizer/prep/preptlist.c | 11 +- src/backend/optimizer/prep/prepunion.c | 1236 --------------------- src/backend/optimizer/util/Makefile | 5 +- src/backend/optimizer/util/append.c | 679 +++++++++++ src/backend/optimizer/util/appendinfo.c | 851 ++++++++++++++ src/backend/optimizer/util/pathnode.c | 1 + src/backend/optimizer/util/plancat.c | 41 +- src/backend/optimizer/util/relnode.c | 81 +- src/backend/partitioning/partprune.c | 44 +- src/include/nodes/relation.h | 8 + src/include/optimizer/append.h | 28 + src/include/optimizer/appendinfo.h | 43 + src/include/optimizer/pathnode.h | 1 + src/include/optimizer/plancat.h | 2 +- src/include/optimizer/prep.h | 20 +- src/include/partitioning/partprune.h | 2 +- src/test/regress/expected/join.out | 22 +- src/test/regress/expected/partition_aggregate.out | 4 +- 25 files changed, 1888 insertions(+), 1612 deletions(-) create mode 100644 src/backend/optimizer/util/append.c create mode 100644 src/backend/optimizer/util/appendinfo.c create mode 100644 src/include/optimizer/append.h create mode 100644 src/include/optimizer/appendinfo.h diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 21a2ef5ad3..c930bf7bab 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7085,15 +7085,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7123,15 +7123,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7160,15 +7160,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.tableoid, foo.* -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.tableoid, foo.* Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.tableoid, foo.* -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.tableoid, foo2.* Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid @@ -7178,15 +7178,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.tableoid, foo.* -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.tableoid, foo.* Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.tableoid, foo.* -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.tableoid, foo2.* Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (39 rows) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 051e4a8e74..971da18c1d 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -30,6 +30,8 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "optimizer/append.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/geqo.h" @@ -44,7 +46,6 @@ #include "optimizer/var.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -942,8 +943,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -951,29 +950,22 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Assert(IS_SIMPLE_REL(rel)); /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, we will bubble up - * the indexes of partitioned relations that appear down in the tree, so - * that when we've created Paths for all the children, the root - * partitioned table's list will contain all such indexes. + * Add append children to the PlannerInfo. */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - rel->partitioned_child_rels = list_make1_int(rti); + expand_append_rel(root, rel, rte, rti); + + /* Did we find out that none of the children produce any output? */ + if (IS_DUMMY_REL(rel)) + return; /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. + * If we only need to scan the parent because all of the children are + * either pruned or excluded, do so right away and return. */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) + if (!rte->inh) { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; + set_rel_size(root, rel, rti, rte); + return; } /* @@ -1013,12 +1005,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; ListCell *parentvars; ListCell *childvars; - ListCell *lc; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1026,18 +1014,31 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); - /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. - */ + /* The child rel's RelOptInfo is created during expand_append_rel */ childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); /* - * Copy/Modify targetlist. Even if this child is deemed empty, we need - * its targetlist in case it falls on nullable side in a child-join - * because of partitionwise join. + * Child relation may have be marked dummy if build_append_child_rel + * found self-contradictory quals. + */ + if (IS_DUMMY_REL(childrel)) + continue; + + if (relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* + * This child need not be scanned, so we can omit it from the + * appendrel. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Copy/Modify targetlist. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -1066,144 +1067,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, add_child_rel_equivalences(root, appinfo, rel, childrel); childrel->has_eclass_joins = rel->has_eclass_joins; - /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* - * This child need not be scanned, so we can omit it from the - * appendrel. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - /* CE failed, so finish copying/modifying join quals. */ childrel->joininfo = (List *) adjust_appendrel_attrs(root, @@ -1274,7 +1137,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, child_relids, top_parent_relids); root->parse = parse; - tlist = preprocess_targetlist(root); + tlist = preprocess_targetlist(root, true); build_base_rel_tlists(root, tlist); root->parse = orig_parse; @@ -2738,6 +2601,7 @@ inheritance_make_rel_from_joinlist(PlannerInfo *root, continue; childrel = find_base_rel(root, appinfo->child_relid); + Assert(childrel != NULL); /* Ignore excluded/pruned children. */ if (IS_DUMMY_REL(childrel)) diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index b22b36ec0e..5ace28eab0 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -22,6 +22,7 @@ #include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index d3d21fed5d..6e321ec9e7 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -15,11 +15,13 @@ #include "postgres.h" #include "miscadmin.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/prep.h" +#include "optimizer/tlist.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -46,6 +48,9 @@ static void try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, List *parent_restrictlist); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + int partidx); /* @@ -1376,6 +1381,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts); + if (child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts); + /* We should never try to join two overlapping sets of rels. */ Assert(!bms_overlap(child_rel1->relids, child_rel2->relids)); child_joinrelids = bms_union(child_rel1->relids, child_rel2->relids); @@ -1582,3 +1592,56 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx) +{ + RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + /* Pass parent's info as for both the parent rel and child rel. */ + AppendRelInfo *appinfo = make_append_rel_info(parent, parentrte, + parent->tupdesc, + parentrte->relid, + parent->reltype, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 01335db511..7636aa82c4 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -616,64 +616,6 @@ create_lateral_join_info(PlannerInfo *root) bms_add_member(brel2->lateral_referencers, rti); } } - - /* - * Lastly, propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - */ - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - RangeTblEntry *brte = root->simple_rte_array[rti]; - - /* - * Skip empty slots. Also skip non-simple relations i.e. dead - * relations. - */ - if (brel == NULL || !IS_SIMPLE_REL(brel)) - continue; - - /* - * In the case of table inheritance, the parent RTE is directly linked - * to every child table via an AppendRelInfo. In the case of table - * partitioning, the inheritance hierarchy is expanded one level at a - * time rather than flattened. Therefore, an other member rel that is - * a partitioned table may have children of its own, and must - * therefore be marked with the appropriate lateral info so that those - * children eventually get marked also. - */ - Assert(brte); - if (brel->reloptkind == RELOPT_OTHER_MEMBER_REL && - (brte->rtekind != RTE_RELATION || - brte->relkind != RELKIND_PARTITIONED_TABLE)) - continue; - - if (brte->inh) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); - RelOptInfo *childrel; - - if (appinfo->parent_relid != rti) - continue; - childrel = root->simple_rel_array[appinfo->child_relid]; - Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - Assert(childrel->direct_lateral_relids == NULL); - childrel->direct_lateral_relids = brel->direct_lateral_relids; - Assert(childrel->lateral_relids == NULL); - childrel->lateral_relids = brel->lateral_relids; - Assert(childrel->lateral_referencers == NULL); - childrel->lateral_referencers = brel->lateral_referencers; - } - } - } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 93b8c761c2..b54dc6c38e 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -23,6 +23,7 @@ #include "access/sysattr.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -38,6 +39,7 @@ #include "nodes/print.h" #endif #include "nodes/relation.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" @@ -699,27 +701,24 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } /* * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). + * pullup (so that all non-inherited RTEs are present). */ preprocess_rowmarks(root); /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain base relations not joins, so it's OK (and marginally more - * efficient) to do it after checking for join RTEs. We must do it after - * pulling up subqueries, else we'd fail to handle inherited tables in - * subqueries. - */ - expand_inherited_tables(root); - - /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -1215,7 +1214,7 @@ inheritance_planner(PlannerInfo *root) orig_tlist = list_copy(root->parse->targetList); /* Do the scan/join planning. */ - tlist = preprocess_targetlist(root); + tlist = preprocess_targetlist(root, false); qp_extra.tlist = tlist; qp_extra.activeWindows = qp_extra.groupClause = NIL; planned_rel = query_planner(root, tlist, standard_qp_callback, &qp_extra); @@ -1223,9 +1222,10 @@ inheritance_planner(PlannerInfo *root) /* * If it turned out during query planning that all the children are dummy * (pruned or excluded by constraints), no need to do the steps below. - * Let grouping_planner finish up the final path. + * For regular inheritance case, it's possible that we only need to modify + * the parent table. Let grouping_planner finish up the final path. */ - if (IS_DUMMY_REL(planned_rel)) + if (!parent_rte->inh || IS_DUMMY_REL(planned_rel)) { grouping_planner(root, false, planned_rel, 0.0); return; @@ -1580,7 +1580,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, } /* Preprocess targetlist */ - tlist = preprocess_targetlist(root); + tlist = preprocess_targetlist(root, inheritance_update); /* * We are now done hacking up the query's targetlist. Most of the @@ -1674,6 +1674,15 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, standard_qp_callback, &qp_extra); /* + * Query planning may have added some columns to the top-level tlist, + * which happens when there row marks applied to inheritance parent + * relations (additional junk columns needed for applying row marks + * are added after expanding inheritance.) + */ + if (list_length(tlist) < list_length(root->processed_tlist)) + tlist = root->processed_tlist; + + /* * Convert the query's result tlist into PathTarget format. * * Note: it's desirable to not do this till after query_planner(), @@ -2360,7 +2369,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2385,7 +2394,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } @@ -6765,6 +6774,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -6866,6 +6879,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 6d6ef1c376..454870609a 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -326,6 +326,12 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) UPPERREL_FINAL, NULL))) add_rtes_to_flat_rtable(rel->subroot, true); } + /* + * A NULL rel also means an unplanned subquery rte, so apply + * flatten_unplanned_rtes. + */ + else + flatten_unplanned_rtes(glob, rte); } rti++; } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 8603feef2b..9c0a1c1b1c 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -67,7 +67,7 @@ static List *expand_targetlist(List *tlist, int command_type, * is also preprocessed (and updated in-place). */ List * -preprocess_targetlist(PlannerInfo *root) +preprocess_targetlist(PlannerInfo *root, bool inheritance_update) { Query *parse = root->parse; int result_relation = parse->resultRelation; @@ -134,6 +134,15 @@ preprocess_targetlist(PlannerInfo *root) if (rc->rti != rc->prti) continue; + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which affects which junk columns + * are needed in the top-level tlist. + */ + if (rc->isParent && !inheritance_update) + continue; + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) { /* Need to fetch TID */ diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index ef1f978889..70f37c593f 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -12,11 +12,6 @@ * case, but most of the heavy lifting for that is done elsewhere, * notably in prepjointree.c and allpaths.c. * - * There is also some code here to support planning of queries that use - * inheritance (SELECT FROM foo*). Inheritance trees are converted into - * append relations, and thenceforth share code with the UNION ALL case. - * - * * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -54,13 +49,6 @@ #include "utils/syscache.h" -typedef struct -{ - PlannerInfo *root; - int nappinfos; - AppendRelInfo **appinfos; -} adjust_appendrel_attrs_context; - static RelOptInfo *recurse_set_operations(Node *setOp, PlannerInfo *root, List *colTypes, List *colCollations, bool junkOK, @@ -99,31 +87,6 @@ static List *generate_append_tlist(List *colTypes, List *colCollations, List *input_tlists, List *refnames_tlist); static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist); -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); -static void expand_partitioned_rtentry(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); -static void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); -static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars); -static Node *adjust_appendrel_attrs_mutator(Node *node, - adjust_appendrel_attrs_context *context); -static Relids adjust_child_relids(Relids relids, int nappinfos, - AppendRelInfo **appinfos); -static List *adjust_inherited_tlist(List *tlist, - AppendRelInfo *context); /* @@ -1460,1202 +1423,3 @@ generate_setop_grouplist(SetOperationStmt *op, List *targetlist) Assert(lg == NULL); return grouplist; } - - -/* - * expand_inherited_tables - * Expand each rangetable entry that represents an inheritance set - * into an "append relation". At the conclusion of this process, - * the "inh" flag is set in all and only those RTEs that are append - * relation parents. - */ -void -expand_inherited_tables(PlannerInfo *root) -{ - Index nrtes; - Index rti; - ListCell *rl; - - /* - * expand_inherited_rtentry may add RTEs to parse->rtable. The function is - * expected to recursively handle any RTEs that it creates with inh=true. - * So just scan as far as the original end of the rtable list. - */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) - { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); - - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); - } -} - -/* - * expand_inherited_rtentry - * Check whether a rangetable entry represents an inheritance set. - * If so, add entries for all the child tables to the query's - * rangetable, and build AppendRelInfo nodes for all the child tables - * and add them to root->append_rel_list. If not, clear the entry's - * "inh" flag to prevent later code from looking for AppendRelInfos. - * - * Note that the original RTE is considered to represent the whole - * inheritance set. The first of the generated RTEs is an RTE for the same - * table, but with inh = false, to represent the parent table in its role - * as a simple member of the inheritance set. - * - * A childless table is never considered to be an inheritance set. For - * regular inheritance, a parent RTE must always have at least two associated - * AppendRelInfos: one corresponding to the parent table as a simple member of - * inheritance set and one or more corresponding to the actual children. - * Since a partitioned table is not scanned, it might have only one associated - * AppendRelInfo. - */ -static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) -{ - Oid parentOID; - PlanRowMark *oldrc; - Relation oldrelation; - LOCKMODE lockmode; - List *inhOIDs; - ListCell *l; - - /* Does RT entry allow inheritance? */ - if (!rte->inh) - return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } - /* Fast path for common case of childless table */ - parentOID = rte->relid; - if (!has_subclass(parentOID)) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * The rewriter should already have obtained an appropriate lock on each - * relation named in the query. However, for each child relation we add - * to the query, we must obtain an appropriate lock, because this will be - * the first use of those relations in the parse/rewrite/plan pipeline. - * Child rels should use the same lockmode as their parent. - */ - lockmode = rte->rellockmode; - - /* Scan for all members of inheritance set, acquire needed locks */ - inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); - - /* - * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite above has_subclass() check, if table - * once had a child but no longer does. - */ - if (list_length(inhOIDs) < 2) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * If parent relation is selected FOR UPDATE/SHARE, we need to mark its - * PlanRowMark as isParent = true, and generate a new PlanRowMark for each - * child. - */ - oldrc = get_plan_rowmark(root->rowMarks, rti); - if (oldrc) - oldrc->isParent = true; - - /* - * Must open the parent relation to examine its tupdesc. We need not lock - * it; we assume the rewriter already did. - */ - oldrelation = heap_open(parentOID, NoLock); - - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) - { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); - - /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. - */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); - } - else - { - List *appinfos = NIL; - RangeTblEntry *childrte; - Index childRTindex; - - /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. - */ - foreach(l, inhOIDs) - { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = heap_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - heap_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - heap_close(newrelation, NoLock); - } - - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); - - } - - heap_close(oldrelation, NoLock); -} - -/* - * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. - * - * Note that RelationGetPartitionDispatchInfo will expand partitions in the - * same order as this code. - */ -static void -expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) -{ - int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); - - check_stack_depth(); - - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); - - /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. - */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); - - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); - - /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. - */ - if (partdesc->nparts == 0) - { - parentrte->inh = false; - return; - } - - for (i = 0; i < partdesc->nparts; i++) - { - Oid childOID = partdesc->oids[i]; - Relation childrel; - - /* Open rel; we already have required locks */ - childrel = heap_open(childOID, NoLock); - - /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. - */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); - - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); - - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); - - /* Close child relation, but keep locks */ - heap_close(childrel, NoLock); - } -} - -/* - * expand_single_inheritance_child - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. - * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) - * - * PlanRowMarks still carry the top-parent's RTI, and the top-parent's - * allMarkTypes field still accumulates values from all descendents. - * - * "parentrte" and "parentRTindex" are immediate parent's RTE and - * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. - */ -static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) -{ - Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); - Oid childOID = RelationGetRelid(childrel); - RangeTblEntry *childrte; - Index childRTindex; - AppendRelInfo *appinfo; - - /* - * Build an RTE for the child, and attach to query's rangetable list. We - * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh = false. Also, set requiredPerms to zero since - * all required permissions checks are done on the original RTE. Likewise, - * set the child's securityQuals to empty, because we only want to apply - * the parent's RLS conditions regardless of what RLS properties - * individual children may have. (This is an intentional choice to make - * inherited RLS work like regular permissions checks.) The parent - * securityQuals will be propagated to children along with other base - * restriction clauses, so we don't need to do it here. - */ - childrte = copyObject(parentrte); - *childrte_p = childrte; - childrte->relid = childOID; - childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) - childrte->inh = true; - else - childrte->inh = false; - childrte->requiredPerms = 0; - childrte->securityQuals = NIL; - parse->rtable = lappend(parse->rtable, childrte); - childRTindex = list_length(parse->rtable); - *childRTindex_p = childRTindex; - - /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. - */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) - { - appinfo = makeNode(AppendRelInfo); - appinfo->parent_relid = parentRTindex; - appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->rd_rel->reltype; - appinfo->child_reltype = childrel->rd_rel->reltype; - make_inh_translation_list(parentrel, childrel, childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = parentOID; - *appinfos = lappend(*appinfos, appinfo); - - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } - } - - /* - * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE. - */ - if (top_parentrc) - { - PlanRowMark *childrc = makeNode(PlanRowMark); - - childrc->rti = childRTindex; - childrc->prti = top_parentrc->rti; - childrc->rowmarkId = top_parentrc->rowmarkId; - /* Reselect rowmark type, because relkind might not match parent */ - childrc->markType = select_rowmark_type(childrte, - top_parentrc->strength); - childrc->allMarkTypes = (1 << childrc->markType); - childrc->strength = top_parentrc->strength; - childrc->waitPolicy = top_parentrc->waitPolicy; - - /* - * We mark RowMarks for partitioned child tables as parent RowMarks so - * that the executor ignores them (except their existence means that - * the child tables be locked using appropriate mode). - */ - childrc->isParent = (childrte->relkind == RELKIND_PARTITIONED_TABLE); - - /* Include child's rowmark type in top parent's allMarkTypes */ - top_parentrc->allMarkTypes |= childrc->allMarkTypes; - - root->rowMarks = lappend(root->rowMarks, childrc); - } -} - -/* - * make_inh_translation_list - * Build the list of translations from parent Vars to child Vars for - * an inheritance child. - * - * For paranoia's sake, we match type/collation as well as attribute name. - */ -static void -make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, - List **translated_vars) -{ - List *vars = NIL; - TupleDesc old_tupdesc = RelationGetDescr(oldrelation); - TupleDesc new_tupdesc = RelationGetDescr(newrelation); - Oid new_relid = RelationGetRelid(newrelation); - int oldnatts = old_tupdesc->natts; - int newnatts = new_tupdesc->natts; - int old_attno; - int new_attno = 0; - - for (old_attno = 0; old_attno < oldnatts; old_attno++) - { - Form_pg_attribute att; - char *attname; - Oid atttypid; - int32 atttypmod; - Oid attcollation; - - att = TupleDescAttr(old_tupdesc, old_attno); - if (att->attisdropped) - { - /* Just put NULL into this list entry */ - vars = lappend(vars, NULL); - continue; - } - attname = NameStr(att->attname); - atttypid = att->atttypid; - atttypmod = att->atttypmod; - attcollation = att->attcollation; - - /* - * When we are generating the "translation list" for the parent table - * of an inheritance set, no need to search for matches. - */ - if (oldrelation == newrelation) - { - vars = lappend(vars, makeVar(newvarno, - (AttrNumber) (old_attno + 1), - atttypid, - atttypmod, - attcollation, - 0)); - continue; - } - - /* - * Otherwise we have to search for the matching column by name. - * There's no guarantee it'll have the same column position, because - * of cases like ALTER TABLE ADD COLUMN and multiple inheritance. - * However, in simple cases, the relative order of columns is mostly - * the same in both relations, so try the column of newrelation that - * follows immediately after the one that we just found, and if that - * fails, let syscache handle it. - */ - if (new_attno >= newnatts || - (att = TupleDescAttr(new_tupdesc, new_attno))->attisdropped || - strcmp(attname, NameStr(att->attname)) != 0) - { - HeapTuple newtup; - - newtup = SearchSysCacheAttName(new_relid, attname); - if (!newtup) - elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, RelationGetRelationName(newrelation)); - new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; - ReleaseSysCache(newtup); - - att = TupleDescAttr(new_tupdesc, new_attno); - } - - /* Found it, check type and collation match */ - if (atttypid != att->atttypid || atttypmod != att->atttypmod) - elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, RelationGetRelationName(newrelation)); - if (attcollation != att->attcollation) - elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, RelationGetRelationName(newrelation)); - - vars = lappend(vars, makeVar(newvarno, - (AttrNumber) (new_attno + 1), - atttypid, - atttypmod, - attcollation, - 0)); - new_attno++; - } - - *translated_vars = vars; -} - -/* - * translate_col_privs - * Translate a bitmapset representing per-column privileges from the - * parent rel's attribute numbering to the child's. - * - * The only surprise here is that we don't translate a parent whole-row - * reference into a child whole-row reference. That would mean requiring - * permissions on all child columns, which is overly strict, since the - * query is really only going to reference the inherited columns. Instead - * we set the per-column bits for all inherited columns. - */ -static Bitmapset * -translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars) -{ - Bitmapset *child_privs = NULL; - bool whole_row; - int attno; - ListCell *lc; - - /* System attributes have the same numbers in all tables */ - for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) - { - if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - attno - FirstLowInvalidHeapAttributeNumber); - } - - /* Check if parent has whole-row reference */ - whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, - parent_privs); - - /* And now translate the regular user attributes, using the vars list */ - attno = InvalidAttrNumber; - foreach(lc, translated_vars) - { - Var *var = lfirst_node(Var, lc); - - attno++; - if (var == NULL) /* ignore dropped columns */ - continue; - if (whole_row || - bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - var->varattno - FirstLowInvalidHeapAttributeNumber); - } - - return child_privs; -} - -/* - * adjust_appendrel_attrs - * Copy the specified query or expression and translate Vars referring to a - * parent rel to refer to the corresponding child rel instead. We also - * update rtindexes appearing outside Vars, such as resultRelation and - * jointree relids. - * - * Note: this is only applied after conversion of sublinks to subplans, - * so we don't need to cope with recursion into sub-queries. - * - * Note: this is not hugely different from what pullup_replace_vars() does; - * maybe we should try to fold the two routines together. - */ -Node * -adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, - AppendRelInfo **appinfos) -{ - Node *result; - adjust_appendrel_attrs_context context; - - context.root = root; - context.nappinfos = nappinfos; - context.appinfos = appinfos; - - /* If there's nothing to adjust, don't call this function. */ - Assert(nappinfos >= 1 && appinfos != NULL); - - /* - * Must be prepared to start with a Query or a bare expression tree. - */ - if (node && IsA(node, Query)) - { - Query *newnode; - int cnt; - - newnode = query_tree_mutator((Query *) node, - adjust_appendrel_attrs_mutator, - (void *) &context, - QTW_IGNORE_RC_SUBQUERIES); - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (newnode->resultRelation == appinfo->parent_relid) - { - newnode->resultRelation = appinfo->child_relid; - /* Fix tlist resnos too, if it's inherited UPDATE */ - if (newnode->commandType == CMD_UPDATE) - newnode->targetList = - adjust_inherited_tlist(newnode->targetList, - appinfo); - break; - } - } - - result = (Node *) newnode; - } - else - result = adjust_appendrel_attrs_mutator(node, &context); - - return result; -} - -static Node * -adjust_appendrel_attrs_mutator(Node *node, - adjust_appendrel_attrs_context *context) -{ - AppendRelInfo **appinfos = context->appinfos; - int nappinfos = context->nappinfos; - int cnt; - - if (node == NULL) - return NULL; - if (IsA(node, Var)) - { - Var *var = (Var *) copyObject(node); - AppendRelInfo *appinfo = NULL; - - for (cnt = 0; cnt < nappinfos; cnt++) - { - if (var->varno == appinfos[cnt]->parent_relid) - { - appinfo = appinfos[cnt]; - break; - } - } - - if (var->varlevelsup == 0 && appinfo) - { - var->varno = appinfo->child_relid; - var->varnoold = appinfo->child_relid; - if (var->varattno > 0) - { - Node *newnode; - - if (var->varattno > list_length(appinfo->translated_vars)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - var->varattno, get_rel_name(appinfo->parent_reloid)); - newnode = copyObject(list_nth(appinfo->translated_vars, - var->varattno - 1)); - if (newnode == NULL) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - var->varattno, get_rel_name(appinfo->parent_reloid)); - return newnode; - } - else if (var->varattno == 0) - { - /* - * Whole-row Var: if we are dealing with named rowtypes, we - * can use a whole-row Var for the child table plus a coercion - * step to convert the tuple layout to the parent's rowtype. - * Otherwise we have to generate a RowExpr. - */ - if (OidIsValid(appinfo->child_reltype)) - { - Assert(var->vartype == appinfo->parent_reltype); - if (appinfo->parent_reltype != appinfo->child_reltype) - { - ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr); - - r->arg = (Expr *) var; - r->resulttype = appinfo->parent_reltype; - r->convertformat = COERCE_IMPLICIT_CAST; - r->location = -1; - /* Make sure the Var node has the right type ID, too */ - var->vartype = appinfo->child_reltype; - return (Node *) r; - } - } - else - { - /* - * Build a RowExpr containing the translated variables. - * - * In practice var->vartype will always be RECORDOID here, - * so we need to come up with some suitable column names. - * We use the parent RTE's column names. - * - * Note: we can't get here for inheritance cases, so there - * is no need to worry that translated_vars might contain - * some dummy NULLs. - */ - RowExpr *rowexpr; - List *fields; - RangeTblEntry *rte; - - rte = rt_fetch(appinfo->parent_relid, - context->root->parse->rtable); - fields = copyObject(appinfo->translated_vars); - rowexpr = makeNode(RowExpr); - rowexpr->args = fields; - rowexpr->row_typeid = var->vartype; - rowexpr->row_format = COERCE_IMPLICIT_CAST; - rowexpr->colnames = copyObject(rte->eref->colnames); - rowexpr->location = -1; - - return (Node *) rowexpr; - } - } - /* system attributes don't need any other translation */ - } - return (Node *) var; - } - if (IsA(node, CurrentOfExpr)) - { - CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node); - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (cexpr->cvarno == appinfo->parent_relid) - { - cexpr->cvarno = appinfo->child_relid; - break; - } - } - return (Node *) cexpr; - } - if (IsA(node, RangeTblRef)) - { - RangeTblRef *rtr = (RangeTblRef *) copyObject(node); - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (rtr->rtindex == appinfo->parent_relid) - { - rtr->rtindex = appinfo->child_relid; - break; - } - } - return (Node *) rtr; - } - if (IsA(node, JoinExpr)) - { - /* Copy the JoinExpr node with correct mutation of subnodes */ - JoinExpr *j; - AppendRelInfo *appinfo; - - j = (JoinExpr *) expression_tree_mutator(node, - adjust_appendrel_attrs_mutator, - (void *) context); - /* now fix JoinExpr's rtindex (probably never happens) */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - appinfo = appinfos[cnt]; - - if (j->rtindex == appinfo->parent_relid) - { - j->rtindex = appinfo->child_relid; - break; - } - } - return (Node *) j; - } - if (IsA(node, PlaceHolderVar)) - { - /* Copy the PlaceHolderVar node with correct mutation of subnodes */ - PlaceHolderVar *phv; - - phv = (PlaceHolderVar *) expression_tree_mutator(node, - adjust_appendrel_attrs_mutator, - (void *) context); - /* now fix PlaceHolderVar's relid sets */ - if (phv->phlevelsup == 0) - phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos, - context->appinfos); - return (Node *) phv; - } - - /* - * This is needed, because inheritance_make_rel_from_joinlist needs to - * translate root->join_info_list executing make_rel_from_joinlist for a - * given child. - */ - if (IsA(node, SpecialJoinInfo)) - { - SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; - SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); - - memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); - newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, - context->nappinfos, - context->appinfos); - newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, - context->nappinfos, - context->appinfos); - newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, - context->nappinfos, - context->appinfos); - newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, - context->nappinfos, - context->appinfos); - newinfo->semi_rhs_exprs = - (List *) expression_tree_mutator((Node *) - oldinfo->semi_rhs_exprs, - adjust_appendrel_attrs_mutator, - (void *) context); - return (Node *) newinfo; - } - - /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, AppendRelInfo)); - Assert(!IsA(node, PlaceHolderInfo)); - Assert(!IsA(node, MinMaxAggInfo)); - - /* - * We have to process RestrictInfo nodes specially. (Note: although - * set_append_rel_pathlist will hide RestrictInfos in the parent's - * baserestrictinfo list from us, it doesn't hide those in joininfo.) - */ - if (IsA(node, RestrictInfo)) - { - RestrictInfo *oldinfo = (RestrictInfo *) node; - RestrictInfo *newinfo = makeNode(RestrictInfo); - - /* Copy all flat-copiable fields */ - memcpy(newinfo, oldinfo, sizeof(RestrictInfo)); - - /* Recursively fix the clause itself */ - newinfo->clause = (Expr *) - adjust_appendrel_attrs_mutator((Node *) oldinfo->clause, context); - - /* and the modified version, if an OR clause */ - newinfo->orclause = (Expr *) - adjust_appendrel_attrs_mutator((Node *) oldinfo->orclause, context); - - /* adjust relid sets too */ - newinfo->clause_relids = adjust_child_relids(oldinfo->clause_relids, - context->nappinfos, - context->appinfos); - newinfo->required_relids = adjust_child_relids(oldinfo->required_relids, - context->nappinfos, - context->appinfos); - newinfo->outer_relids = adjust_child_relids(oldinfo->outer_relids, - context->nappinfos, - context->appinfos); - newinfo->nullable_relids = adjust_child_relids(oldinfo->nullable_relids, - context->nappinfos, - context->appinfos); - newinfo->left_relids = adjust_child_relids(oldinfo->left_relids, - context->nappinfos, - context->appinfos); - newinfo->right_relids = adjust_child_relids(oldinfo->right_relids, - context->nappinfos, - context->appinfos); - - /* - * Reset cached derivative fields, since these might need to have - * different values when considering the child relation. Note we - * don't reset left_ec/right_ec: each child variable is implicitly - * equivalent to its parent, so still a member of the same EC if any. - */ - newinfo->eval_cost.startup = -1; - newinfo->norm_selec = -1; - newinfo->outer_selec = -1; - newinfo->left_em = NULL; - newinfo->right_em = NULL; - newinfo->scansel_cache = NIL; - newinfo->left_bucketsize = -1; - newinfo->right_bucketsize = -1; - newinfo->left_mcvfreq = -1; - newinfo->right_mcvfreq = -1; - - return (Node *) newinfo; - } - - /* - * NOTE: we do not need to recurse into sublinks, because they should - * already have been converted to subplans before we see them. - */ - Assert(!IsA(node, SubLink)); - Assert(!IsA(node, Query)); - - return expression_tree_mutator(node, adjust_appendrel_attrs_mutator, - (void *) context); -} - -/* - * Substitute child relids for parent relids in a Relid set. The array of - * appinfos specifies the substitutions to be performed. - */ -static Relids -adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos) -{ - Bitmapset *result = NULL; - int cnt; - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - /* Remove parent, add child */ - if (bms_is_member(appinfo->parent_relid, relids)) - { - /* Make a copy if we are changing the set. */ - if (!result) - result = bms_copy(relids); - - result = bms_del_member(result, appinfo->parent_relid); - result = bms_add_member(result, appinfo->child_relid); - } - } - - /* If we made any changes, return the modified copy. */ - if (result) - return result; - - /* Otherwise, return the original set without modification. */ - return relids; -} - -/* - * Replace any relid present in top_parent_relids with its child in - * child_relids. Members of child_relids can be multiple levels below top - * parent in the partition hierarchy. - */ -Relids -adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, - Relids child_relids, Relids top_parent_relids) -{ - AppendRelInfo **appinfos; - int nappinfos; - Relids parent_relids = NULL; - Relids result; - Relids tmp_result = NULL; - int cnt; - - /* - * If the given relids set doesn't contain any of the top parent relids, - * it will remain unchanged. - */ - if (!bms_overlap(relids, top_parent_relids)) - return relids; - - appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); - - /* Construct relids set for the immediate parent of the given child. */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); - } - - /* Recurse if immediate parent is not the top parent. */ - if (!bms_equal(parent_relids, top_parent_relids)) - { - tmp_result = adjust_child_relids_multilevel(root, relids, - parent_relids, - top_parent_relids); - relids = tmp_result; - } - - result = adjust_child_relids(relids, nappinfos, appinfos); - - /* Free memory consumed by any intermediate result. */ - if (tmp_result) - bms_free(tmp_result); - bms_free(parent_relids); - pfree(appinfos); - - return result; -} - -/* - * Adjust the targetlist entries of an inherited UPDATE operation - * - * The expressions have already been fixed, but we have to make sure that - * the target resnos match the child table (they may not, in the case of - * a column that was added after-the-fact by ALTER TABLE). In some cases - * this can force us to re-order the tlist to preserve resno ordering. - * (We do all this work in special cases so that preptlist.c is fast for - * the typical case.) - * - * The given tlist has already been through expression_tree_mutator; - * therefore the TargetEntry nodes are fresh copies that it's okay to - * scribble on. - * - * Note that this is not needed for INSERT because INSERT isn't inheritable. - */ -static List * -adjust_inherited_tlist(List *tlist, AppendRelInfo *context) -{ - bool changed_it = false; - ListCell *tl; - List *new_tlist; - bool more; - int attrno; - - /* This should only happen for an inheritance case, not UNION ALL */ - Assert(OidIsValid(context->parent_reloid)); - - /* Scan tlist and update resnos to match attnums of child rel */ - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - Var *childvar; - - if (tle->resjunk) - continue; /* ignore junk items */ - - /* Look up the translation of this column: it must be a Var */ - if (tle->resno <= 0 || - tle->resno > list_length(context->translated_vars)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - tle->resno, get_rel_name(context->parent_reloid)); - childvar = (Var *) list_nth(context->translated_vars, tle->resno - 1); - if (childvar == NULL || !IsA(childvar, Var)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - tle->resno, get_rel_name(context->parent_reloid)); - - if (tle->resno != childvar->varattno) - { - tle->resno = childvar->varattno; - changed_it = true; - } - } - - /* - * If we changed anything, re-sort the tlist by resno, and make sure - * resjunk entries have resnos above the last real resno. The sort - * algorithm is a bit stupid, but for such a seldom-taken path, small is - * probably better than fast. - */ - if (!changed_it) - return tlist; - - new_tlist = NIL; - more = true; - for (attrno = 1; more; attrno++) - { - more = false; - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - - if (tle->resjunk) - continue; /* ignore junk items */ - - if (tle->resno == attrno) - new_tlist = lappend(new_tlist, tle); - else if (tle->resno > attrno) - more = true; - } - } - - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - - if (!tle->resjunk) - continue; /* here, ignore non-junk items */ - - tle->resno = attrno; - new_tlist = lappend(new_tlist, tle); - attrno++; - } - - return new_tlist; -} - -/* - * adjust_appendrel_attrs_multilevel - * Apply Var translations from a toplevel appendrel parent down to a child. - * - * In some cases we need to translate expressions referencing a parent relation - * to reference an appendrel child that's multiple levels removed from it. - */ -Node * -adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, - Relids child_relids, - Relids top_parent_relids) -{ - AppendRelInfo **appinfos; - Bitmapset *parent_relids = NULL; - int nappinfos; - int cnt; - - Assert(bms_num_members(child_relids) == bms_num_members(top_parent_relids)); - - appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); - - /* Construct relids set for the immediate parent of given child. */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); - } - - /* Recurse if immediate parent is not the top parent. */ - if (!bms_equal(parent_relids, top_parent_relids)) - node = adjust_appendrel_attrs_multilevel(root, node, parent_relids, - top_parent_relids); - - /* Now translate for this child */ - node = adjust_appendrel_attrs(root, node, nappinfos, appinfos); - - pfree(appinfos); - - return node; -} - -/* - * Construct the SpecialJoinInfo for a child-join by translating - * SpecialJoinInfo for the join between parents. left_relids and right_relids - * are the relids of left and right side of the join respectively. - */ -SpecialJoinInfo * -build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, - Relids left_relids, Relids right_relids) -{ - SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo); - AppendRelInfo **left_appinfos; - int left_nappinfos; - AppendRelInfo **right_appinfos; - int right_nappinfos; - - memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo)); - left_appinfos = find_appinfos_by_relids(root, left_relids, - &left_nappinfos); - right_appinfos = find_appinfos_by_relids(root, right_relids, - &right_nappinfos); - - sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand, - left_nappinfos, left_appinfos); - sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand, - right_nappinfos, - right_appinfos); - sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand, - left_nappinfos, left_appinfos); - sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand, - right_nappinfos, - right_appinfos); - sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root, - (Node *) sjinfo->semi_rhs_exprs, - right_nappinfos, - right_appinfos); - - pfree(left_appinfos); - pfree(right_appinfos); - - return sjinfo; -} - -/* - * find_appinfos_by_relids - * Find AppendRelInfo structures for all relations specified by relids. - * - * The AppendRelInfos are returned in an array, which can be pfree'd by the - * caller. *nappinfos is set to the number of entries in the array. - */ -AppendRelInfo ** -find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos) -{ - AppendRelInfo **appinfos; - int cnt = 0; - int i; - - *nappinfos = bms_num_members(relids); - appinfos = (AppendRelInfo **) palloc(sizeof(AppendRelInfo *) * *nappinfos); - - i = -1; - while ((i = bms_next_member(relids, i)) >= 0) - { - AppendRelInfo *appinfo = root->append_rel_array[i]; - - if (!appinfo) - elog(ERROR, "child rel %d not found in append_rel_array", i); - - appinfos[cnt++] = appinfo; - } - return appinfos; -} diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile index c54d0a690d..d166030a10 100644 --- a/src/backend/optimizer/util/Makefile +++ b/src/backend/optimizer/util/Makefile @@ -12,7 +12,8 @@ subdir = src/backend/optimizer/util top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = clauses.o joininfo.o orclauses.o pathnode.o placeholder.o \ - plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o +OBJS = append.o appendinfo.o clauses.o joininfo.o orclauses.o pathnode.o \ + placeholder.o plancat.o predtest.o relnode.o restrictinfo.o tlist.o \ + var.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/optimizer/util/append.c b/src/backend/optimizer/util/append.c new file mode 100644 index 0000000000..76d9246d5f --- /dev/null +++ b/src/backend/optimizer/util/append.c @@ -0,0 +1,679 @@ +/*------------------------------------------------------------------------- + * + * append.c + * Routines to process children of an appendrel parent + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/optimizer/path/append.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <limits.h> + +#include "access/sysattr.h" +#include "catalog/partition.h" +#include "catalog/pg_class.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_type.h" +#include "nodes/makefuncs.h" +#include "optimizer/append.h" +#include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" +#include "optimizer/plancat.h" +#include "optimizer/planmain.h" +#include "optimizer/planner.h" +#include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" +#include "optimizer/tlist.h" +#include "optimizer/var.h" +#include "partitioning/partprune.h" +#include "utils/rel.h" + +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *rel, + RangeTblEntry *rte, + Oid childOID, + LOCKMODE lockmode, + PlanRowMark *top_parentrc); +static RelOptInfo *build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); + +/* + * expand_append_rel + * This initializes RelOptInfos for an appendrel's child relations. + * + * 'rel' is the appendrel parent, whose range table entry ('rte') has been + * marked to require adding children. An appendrel parent could either + * be a subquery (if we flattened UNION ALL query) or a table that's known + * to have inheritance children. The latter consists of both regular + * inheritance parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as an appendrel child. The original RTE is + * considered to represent the whole inheritance set. + */ +void +expand_append_rel(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, + Index rti) +{ + LOCKMODE lockmode; + ListCell *l; + + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + if (rte->rtekind == RTE_RELATION) + { + lockmode = rte->rellockmode; + Assert(has_subclass(rte->relid)); + } + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_append_child_rel(root, rel, appinfo->child_relid); + } + } + else if (rte->relkind == RELKIND_PARTITIONED_TABLE) + { + Index root_parent = rel->inh_root_parent > 0 ? + rel->inh_root_parent : + rel->relid; + PlanRowMark *rootrc = NULL; + int i; + Bitmapset *partindexes; + + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); + + /* If root partitioned table, lock *all* partitions in the tree. */ + if (rel->relid == root_parent) + (void) find_all_inheritors(rte->relid, lockmode, NULL); + + /* + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, we will bubble + * up the indexes of partitioned relations that appear down in the + * tree, so that when we've created Paths for all the children, the + * root partitioned table's list will contain all such indexes. + */ + rel->partitioned_child_rels = list_make1_int(rti); + + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(rel); + + /* Must expand PlannerInfo arrays before we can add children. */ + expand_planner_arrays(root, bms_num_members(partindexes)); + + /* + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. + */ + rel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + rel->nparts); + + rootrc = get_plan_rowmark(root->rowMarks, root_parent); + Assert(rootrc == NULL || rootrc->isParent); + + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) + { + Oid childOID = rel->part_oids[i]; + RelOptInfo *childrel; + + /* Already locked, so pass NoLock. */ + childrel = add_inheritance_child_rel(root, rel, rte, + childOID, + NoLock, + rootrc); + rel->part_rels[i] = childrel; + } + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); + } + + } + else + { + /* + * Regular inheritance case, where each child must be opened and + * checked if it can be excluded. + */ + PlanRowMark *oldrc; + List *inhOIDs; + int num_children; + int num_children_added = 0; + + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); + + /* Scan for all members of inheritance set, acquire needed locks */ + inhOIDs = find_all_inheritors(rte->relid, lockmode, NULL); + num_children = list_length(inhOIDs); + + /* + * Check that there's at least one descendant, else treat as no-child + * case. This could happen despite the has_subclass() check passing, + * if table once had a child but no longer does (relhassubclass is + * only opportunistically reset). + */ + if (num_children < 2) + { + /* Clear flag before returning */ + rte->inh = false; + return; + } + + /* + * If parent relation is selected FOR UPDATE/SHARE, we need to mark + * its PlanRowMark as isParent = true, and generate a new PlanRowMark + * for each child. + */ + oldrc = get_plan_rowmark(root->rowMarks, rti); + Assert(oldrc == NULL || oldrc->isParent); + + /* + * Must expand PlannerInfo arrays by num_children before we can add + * children. + */ + expand_planner_arrays(root, num_children); + + foreach(l, inhOIDs) + { + Oid childOID = lfirst_oid(l); + + /* + * We pass NoLock here, because find_all_inheritors already got + * the lock. + */ + if (add_inheritance_child_rel(root, rel, rte, childOID, NoLock, + oldrc) != NULL) + num_children_added++; + } + + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + /* + * If all the children were temp tables, pretend it's a + * non-inheritance situation; we don't need Append node in that case. + * The duplicate RTE we added for the parent table is harmless, so we + * don't bother to get rid of it; ditto for the useless PlanRowMark + * node. + */ + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } + } +} + +/* + * add_inheritance_child_rel + * This conditionally adds various objects needed for planning an + * inheritance child table + * + * The added objects include a RangeTblEntry, an AppendRelInfo, a PlanRowMark, + * and finally a RelOptInfo. Return value is the RelOptInfo if one is added, + * NULL otherwise. + * + * If the child is a partitioned table with 0 partitions or if it's a + * temporary table from another session, we cannot get any data from it, + * so we don't add anything in that case. + */ +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + RangeTblEntry *rte, + Oid childOID, + LOCKMODE lockmode, + PlanRowMark *top_parentrc) +{ + Query *parse = root->parse; + Relation childrel; + Index childRTindex; + RangeTblEntry *childrte; + AppendRelInfo *appinfo; + RelOptInfo *childrelopt; + + childrel = heap_open(childOID, lockmode); + + /* + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. + */ + if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + PartitionDesc partdesc = RelationGetPartitionDesc(childrel); + + Assert(!RELATION_IS_OTHER_TEMP(childrel)); + if (partdesc->nparts == 0) + { + heap_close(childrel, NoLock); + return NULL; + } + } + + /* + * If childrel doesn't belong to this session, skip it, also relinquishing + * the lock. + */ + if (RELATION_IS_OTHER_TEMP(childrel)) + { + heap_close(childrel, lockmode); + return NULL; + } + + /* + * Build an RTE for the child, and attach to query's rangetable list. We + * copy most fields of the parent's RTE, but replace relation OID and + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties + * individual children may have. (This is an intentional choice to make + * inherited RLS work like regular permissions checks.) The parent + * securityQuals will be propagated to children along with other base + * restriction clauses, so we don't need to do it here. + */ + childrte = copyObject(rte); + childrte->relid = RelationGetRelid(childrel); + childrte->relkind = RelationGetForm(childrel)->relkind; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); + childrte->requiredPerms = 0; + childrte->securityQuals = NIL; + parse->rtable = lappend(parse->rtable, childrte); + childRTindex = list_length(parse->rtable); + + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parent, rte, + RelationGetDescr(childrel), + RelationGetRelid(childrel), + RelationGetForm(childrel)->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); + + /* Close the child relation, but keep the lock. */ + heap_close(childrel, NoLock); + + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != rte->relid) + { + childrte->selectedCols = translate_col_privs(rte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(rte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(rte->updatedCols, + appinfo->translated_vars); + } + + /* Add child PlanRowMark. */ + if (top_parentrc) + { + PlanRowMark *childrc = makeNode(PlanRowMark); + + childrc->rti = list_length(parse->rtable); + childrc->prti = top_parentrc->rti; + childrc->rowmarkId = top_parentrc->rowmarkId; + /* Reselect rowmark type, because relkind might not match parent */ + childrc->markType = select_rowmark_type(childrte, top_parentrc->strength); + childrc->allMarkTypes = (1 << childrc->markType); + childrc->strength = top_parentrc->strength; + childrc->waitPolicy = top_parentrc->waitPolicy; + + /* + * We mark RowMarks for partitioned child tables as parent RowMarks so + * that the executor ignores them (except their existence means that + * the child tables be locked using appropriate mode). + */ + childrc->isParent = (childrte->relkind == RELKIND_PARTITIONED_TABLE); + + /* Include child's rowmark type in top parent's allMarkTypes */ + top_parentrc->allMarkTypes |= childrc->allMarkTypes; + root->rowMarks = lappend(root->rowMarks, childrc); + } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need need to create + * one, because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_append_child_rel(root, parent, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; +} + +/* + * build_append_child_rel + * Build a RelOptInfo for child relation of an append rel + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields base on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turns out to be self-contradictory, the + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + List *childquals; + ListCell *lc; + bool have_const_false_cq; + Index cq_min_security; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = false; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, parent->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual = (Node *) rinfo->clause; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, childqual, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might + * have securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals.) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except + * that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* + * not likely that we'd see constants here, so no + * check + */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, + false, + false, + security_level, + NULL, NULL, + NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* Set child's version of baserestrictinfo. */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; +} diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c new file mode 100644 index 0000000000..24d1e15cf9 --- /dev/null +++ b/src/backend/optimizer/util/appendinfo.c @@ -0,0 +1,851 @@ +/*------------------------------------------------------------------------- + * + * appendinfo.c + * Routines for mapping between append parent(s) and children + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/optimizer/path/appendinfo.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" +#include "parser/parsetree.h" +#include "utils/rel.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" + +typedef struct +{ + PlannerInfo *root; + int nappinfos; + AppendRelInfo **appinfos; +} adjust_appendrel_attrs_context; + +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); +static Node *adjust_appendrel_attrs_mutator(Node *node, + adjust_appendrel_attrs_context *context); +static Relids adjust_child_relids(Relids relids, int nappinfos, + AppendRelInfo **appinfos); +static List *adjust_inherited_tlist(List *tlist, + AppendRelInfo *context); + +AppendRelInfo * +make_append_rel_info(RelOptInfo *parentrel, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex) +{ + AppendRelInfo *appinfo = makeNode(AppendRelInfo); + + appinfo->parent_relid = parentrel->relid; + appinfo->child_relid = childRTindex; + appinfo->parent_reltype = parentrel->reltype; + appinfo->child_reltype = childreltype; + make_inh_translation_list(parentrel->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, + &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; + + return appinfo; +} + +/* + * translate_col_privs + * Translate a bitmapset representing per-column privileges from the + * parent rel's attribute numbering to the child's. + * + * The only surprise here is that we don't translate a parent whole-row + * reference into a child whole-row reference. That would mean requiring + * permissions on all child columns, which is overly strict, since the + * query is really only going to reference the inherited columns. Instead + * we set the per-column bits for all inherited columns. + */ +Bitmapset * +translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars) +{ + Bitmapset *child_privs = NULL; + bool whole_row; + int attno; + ListCell *lc; + + /* System attributes have the same numbers in all tables */ + for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) + { + if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + attno - FirstLowInvalidHeapAttributeNumber); + } + + /* Check if parent has whole-row reference */ + whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, + parent_privs); + + /* And now translate the regular user attributes, using the vars list */ + attno = InvalidAttrNumber; + foreach(lc, translated_vars) + { + Var *var = lfirst_node(Var, lc); + + attno++; + if (var == NULL) /* ignore dropped columns */ + continue; + if (whole_row || + bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + var->varattno - FirstLowInvalidHeapAttributeNumber); + } + + return child_privs; +} + +/* + * adjust_appendrel_attrs + * Copy the specified query or expression and translate Vars referring to a + * parent rel to refer to the corresponding child rel instead. We also + * update rtindexes appearing outside Vars, such as resultRelation and + * jointree relids. + * + * Note: this is only applied after conversion of sublinks to subplans, + * so we don't need to cope with recursion into sub-queries. + * + * Note: this is not hugely different from what pullup_replace_vars() does; + * maybe we should try to fold the two routines together. + */ +Node * +adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, + AppendRelInfo **appinfos) +{ + Node *result; + adjust_appendrel_attrs_context context; + + context.root = root; + context.nappinfos = nappinfos; + context.appinfos = appinfos; + + /* If there's nothing to adjust, don't call this function. */ + Assert(nappinfos >= 1 && appinfos != NULL); + + /* + * Must be prepared to start with a Query or a bare expression tree. + */ + if (node && IsA(node, Query)) + { + Query *newnode; + int cnt; + + newnode = query_tree_mutator((Query *) node, + adjust_appendrel_attrs_mutator, + (void *) &context, + QTW_IGNORE_RC_SUBQUERIES); + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (newnode->resultRelation == appinfo->parent_relid) + { + newnode->resultRelation = appinfo->child_relid; + /* Fix tlist resnos too, if it's inherited UPDATE */ + if (newnode->commandType == CMD_UPDATE) + newnode->targetList = + adjust_inherited_tlist(newnode->targetList, + appinfo); + break; + } + } + + result = (Node *) newnode; + } + else + result = adjust_appendrel_attrs_mutator(node, &context); + + return result; +} + +static Node * +adjust_appendrel_attrs_mutator(Node *node, + adjust_appendrel_attrs_context *context) +{ + AppendRelInfo **appinfos = context->appinfos; + int nappinfos = context->nappinfos; + int cnt; + + if (node == NULL) + return NULL; + if (IsA(node, Var)) + { + Var *var = (Var *) copyObject(node); + AppendRelInfo *appinfo = NULL; + + for (cnt = 0; cnt < nappinfos; cnt++) + { + if (var->varno == appinfos[cnt]->parent_relid) + { + appinfo = appinfos[cnt]; + break; + } + } + + if (var->varlevelsup == 0 && appinfo) + { + var->varno = appinfo->child_relid; + var->varnoold = appinfo->child_relid; + if (var->varattno > 0) + { + Node *newnode; + + if (var->varattno > list_length(appinfo->translated_vars)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + var->varattno, get_rel_name(appinfo->parent_reloid)); + newnode = copyObject(list_nth(appinfo->translated_vars, + var->varattno - 1)); + if (newnode == NULL) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + var->varattno, get_rel_name(appinfo->parent_reloid)); + return newnode; + } + else if (var->varattno == 0) + { + /* + * Whole-row Var: if we are dealing with named rowtypes, we + * can use a whole-row Var for the child table plus a coercion + * step to convert the tuple layout to the parent's rowtype. + * Otherwise we have to generate a RowExpr. + */ + if (OidIsValid(appinfo->child_reltype)) + { + Assert(var->vartype == appinfo->parent_reltype); + if (appinfo->parent_reltype != appinfo->child_reltype) + { + ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr); + + r->arg = (Expr *) var; + r->resulttype = appinfo->parent_reltype; + r->convertformat = COERCE_IMPLICIT_CAST; + r->location = -1; + /* Make sure the Var node has the right type ID, too */ + var->vartype = appinfo->child_reltype; + return (Node *) r; + } + } + else + { + /* + * Build a RowExpr containing the translated variables. + * + * In practice var->vartype will always be RECORDOID here, + * so we need to come up with some suitable column names. + * We use the parent RTE's column names. + * + * Note: we can't get here for inheritance cases, so there + * is no need to worry that translated_vars might contain + * some dummy NULLs. + */ + RowExpr *rowexpr; + List *fields; + RangeTblEntry *rte; + + rte = rt_fetch(appinfo->parent_relid, + context->root->parse->rtable); + fields = copyObject(appinfo->translated_vars); + rowexpr = makeNode(RowExpr); + rowexpr->args = fields; + rowexpr->row_typeid = var->vartype; + rowexpr->row_format = COERCE_IMPLICIT_CAST; + rowexpr->colnames = copyObject(rte->eref->colnames); + rowexpr->location = -1; + + return (Node *) rowexpr; + } + } + /* system attributes don't need any other translation */ + } + return (Node *) var; + } + if (IsA(node, CurrentOfExpr)) + { + CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node); + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (cexpr->cvarno == appinfo->parent_relid) + { + cexpr->cvarno = appinfo->child_relid; + break; + } + } + return (Node *) cexpr; + } + if (IsA(node, RangeTblRef)) + { + RangeTblRef *rtr = (RangeTblRef *) copyObject(node); + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (rtr->rtindex == appinfo->parent_relid) + { + rtr->rtindex = appinfo->child_relid; + break; + } + } + return (Node *) rtr; + } + if (IsA(node, JoinExpr)) + { + /* Copy the JoinExpr node with correct mutation of subnodes */ + JoinExpr *j; + AppendRelInfo *appinfo; + + j = (JoinExpr *) expression_tree_mutator(node, + adjust_appendrel_attrs_mutator, + (void *) context); + /* now fix JoinExpr's rtindex (probably never happens) */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + appinfo = appinfos[cnt]; + + if (j->rtindex == appinfo->parent_relid) + { + j->rtindex = appinfo->child_relid; + break; + } + } + return (Node *) j; + } + if (IsA(node, PlaceHolderVar)) + { + /* Copy the PlaceHolderVar node with correct mutation of subnodes */ + PlaceHolderVar *phv; + + phv = (PlaceHolderVar *) expression_tree_mutator(node, + adjust_appendrel_attrs_mutator, + (void *) context); + /* now fix PlaceHolderVar's relid sets */ + if (phv->phlevelsup == 0) + phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos, + context->appinfos); + return (Node *) phv; + } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + + /* Shouldn't need to handle planner auxiliary nodes here */ + Assert(!IsA(node, AppendRelInfo)); + Assert(!IsA(node, PlaceHolderInfo)); + Assert(!IsA(node, MinMaxAggInfo)); + + /* + * We have to process RestrictInfo nodes specially. (Note: although + * set_append_rel_pathlist will hide RestrictInfos in the parent's + * baserestrictinfo list from us, it doesn't hide those in joininfo.) + */ + if (IsA(node, RestrictInfo)) + { + RestrictInfo *oldinfo = (RestrictInfo *) node; + RestrictInfo *newinfo = makeNode(RestrictInfo); + + /* Copy all flat-copiable fields */ + memcpy(newinfo, oldinfo, sizeof(RestrictInfo)); + + /* Recursively fix the clause itself */ + newinfo->clause = (Expr *) + adjust_appendrel_attrs_mutator((Node *) oldinfo->clause, context); + + /* and the modified version, if an OR clause */ + newinfo->orclause = (Expr *) + adjust_appendrel_attrs_mutator((Node *) oldinfo->orclause, context); + + /* adjust relid sets too */ + newinfo->clause_relids = adjust_child_relids(oldinfo->clause_relids, + context->nappinfos, + context->appinfos); + newinfo->required_relids = adjust_child_relids(oldinfo->required_relids, + context->nappinfos, + context->appinfos); + newinfo->outer_relids = adjust_child_relids(oldinfo->outer_relids, + context->nappinfos, + context->appinfos); + newinfo->nullable_relids = adjust_child_relids(oldinfo->nullable_relids, + context->nappinfos, + context->appinfos); + newinfo->left_relids = adjust_child_relids(oldinfo->left_relids, + context->nappinfos, + context->appinfos); + newinfo->right_relids = adjust_child_relids(oldinfo->right_relids, + context->nappinfos, + context->appinfos); + + /* + * Reset cached derivative fields, since these might need to have + * different values when considering the child relation. Note we + * don't reset left_ec/right_ec: each child variable is implicitly + * equivalent to its parent, so still a member of the same EC if any. + */ + newinfo->eval_cost.startup = -1; + newinfo->norm_selec = -1; + newinfo->outer_selec = -1; + newinfo->left_em = NULL; + newinfo->right_em = NULL; + newinfo->scansel_cache = NIL; + newinfo->left_bucketsize = -1; + newinfo->right_bucketsize = -1; + newinfo->left_mcvfreq = -1; + newinfo->right_mcvfreq = -1; + + return (Node *) newinfo; + } + + /* + * NOTE: we do not need to recurse into sublinks, because they should + * already have been converted to subplans before we see them. + */ + Assert(!IsA(node, SubLink)); + Assert(!IsA(node, Query)); + + return expression_tree_mutator(node, adjust_appendrel_attrs_mutator, + (void *) context); +} + +/* + * Substitute child relids for parent relids in a Relid set. The array of + * appinfos specifies the substitutions to be performed. + */ +static Relids +adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos) +{ + Bitmapset *result = NULL; + int cnt; + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + /* Remove parent, add child */ + if (bms_is_member(appinfo->parent_relid, relids)) + { + /* Make a copy if we are changing the set. */ + if (!result) + result = bms_copy(relids); + + result = bms_del_member(result, appinfo->parent_relid); + result = bms_add_member(result, appinfo->child_relid); + } + } + + /* If we made any changes, return the modified copy. */ + if (result) + return result; + + /* Otherwise, return the original set without modification. */ + return relids; +} + +/* + * Replace any relid present in top_parent_relids with its child in + * child_relids. Members of child_relids can be multiple levels below top + * parent in the partition hierarchy. + */ +Relids +adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, + Relids child_relids, Relids top_parent_relids) +{ + AppendRelInfo **appinfos; + int nappinfos; + Relids parent_relids = NULL; + Relids result; + Relids tmp_result = NULL; + int cnt; + + /* + * If the given relids set doesn't contain any of the top parent relids, + * it will remain unchanged. + */ + if (!bms_overlap(relids, top_parent_relids)) + return relids; + + appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); + + /* Construct relids set for the immediate parent of the given child. */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); + } + + /* Recurse if immediate parent is not the top parent. */ + if (!bms_equal(parent_relids, top_parent_relids)) + { + tmp_result = adjust_child_relids_multilevel(root, relids, + parent_relids, + top_parent_relids); + relids = tmp_result; + } + + result = adjust_child_relids(relids, nappinfos, appinfos); + + /* Free memory consumed by any intermediate result. */ + if (tmp_result) + bms_free(tmp_result); + bms_free(parent_relids); + pfree(appinfos); + + return result; +} + +/* + * adjust_appendrel_attrs_multilevel + * Apply Var translations from a toplevel appendrel parent down to a child. + * + * In some cases we need to translate expressions referencing a parent relation + * to reference an appendrel child that's multiple levels removed from it. + */ +Node * +adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, + Relids child_relids, + Relids top_parent_relids) +{ + AppendRelInfo **appinfos; + Bitmapset *parent_relids = NULL; + int nappinfos; + int cnt; + + Assert(bms_num_members(child_relids) == bms_num_members(top_parent_relids)); + + appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); + + /* Construct relids set for the immediate parent of given child. */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); + } + + /* Recurse if immediate parent is not the top parent. */ + if (!bms_equal(parent_relids, top_parent_relids)) + node = adjust_appendrel_attrs_multilevel(root, node, parent_relids, + top_parent_relids); + + /* Now translate for this child */ + node = adjust_appendrel_attrs(root, node, nappinfos, appinfos); + + pfree(appinfos); + + return node; +} + +/* + * Construct the SpecialJoinInfo for a child-join by translating + * SpecialJoinInfo for the join between parents. left_relids and right_relids + * are the relids of left and right side of the join respectively. + */ +SpecialJoinInfo * +build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, + Relids left_relids, Relids right_relids) +{ + SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo); + AppendRelInfo **left_appinfos; + int left_nappinfos; + AppendRelInfo **right_appinfos; + int right_nappinfos; + + memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo)); + left_appinfos = find_appinfos_by_relids(root, left_relids, + &left_nappinfos); + right_appinfos = find_appinfos_by_relids(root, right_relids, + &right_nappinfos); + + sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand, + left_nappinfos, left_appinfos); + sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand, + right_nappinfos, + right_appinfos); + sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand, + left_nappinfos, left_appinfos); + sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand, + right_nappinfos, + right_appinfos); + sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root, + (Node *) sjinfo->semi_rhs_exprs, + right_nappinfos, + right_appinfos); + + pfree(left_appinfos); + pfree(right_appinfos); + + return sjinfo; +} + +/* + * find_appinfos_by_relids + * Find AppendRelInfo structures for all relations specified by relids. + * + * The AppendRelInfos are returned in an array, which can be pfree'd by the + * caller. *nappinfos is set to the number of entries in the array. + */ +AppendRelInfo ** +find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos) +{ + AppendRelInfo **appinfos; + int cnt = 0; + int i; + + *nappinfos = bms_num_members(relids); + appinfos = (AppendRelInfo **) palloc(sizeof(AppendRelInfo *) * *nappinfos); + + i = -1; + while ((i = bms_next_member(relids, i)) >= 0) + { + AppendRelInfo *appinfo = root->append_rel_array[i]; + + if (!appinfo) + elog(ERROR, "child rel %d not found in append_rel_array", i); + + appinfos[cnt++] = appinfo; + } + return appinfos; +} + +/* + * make_inh_translation_list + * Build the list of translations from parent Vars to child Vars for + * an inheritance child. + * + * For paranoia's sake, we match type/collation as well as attribute name. + */ +static void +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) +{ + List *vars = NIL; + int oldnatts = old_tupdesc->natts; + int newnatts = new_tupdesc->natts; + int old_attno; + int new_attno = 0; + + for (old_attno = 0; old_attno < oldnatts; old_attno++) + { + Form_pg_attribute att; + char *attname; + Oid atttypid; + int32 atttypmod; + Oid attcollation; + + att = TupleDescAttr(old_tupdesc, old_attno); + if (att->attisdropped) + { + /* Just put NULL into this list entry */ + vars = lappend(vars, NULL); + continue; + } + attname = NameStr(att->attname); + atttypid = att->atttypid; + atttypmod = att->atttypmod; + attcollation = att->attcollation; + + /* + * When we are generating the "translation list" for the parent table + * of an inheritance set, no need to search for matches. + */ + if (from_rel == to_rel) + { + vars = lappend(vars, makeVar(newvarno, + (AttrNumber) (old_attno + 1), + atttypid, + atttypmod, + attcollation, + 0)); + continue; + } + + /* + * Otherwise we have to search for the matching column by name. + * There's no guarantee it'll have the same column position, because + * of cases like ALTER TABLE ADD COLUMN and multiple inheritance. + * However, in simple cases, the relative order of columns is mostly + * the same in both relations, so try the column of newrelation that + * follows immediately after the one that we just found, and if that + * fails, let syscache handle it. + */ + if (new_attno >= newnatts || + (att = TupleDescAttr(new_tupdesc, new_attno))->attisdropped || + strcmp(attname, NameStr(att->attname)) != 0) + { + HeapTuple newtup; + + newtup = SearchSysCacheAttName(to_rel, attname); + if (!newtup) + elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", + attname, get_rel_name(to_rel)); + new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; + ReleaseSysCache(newtup); + + att = TupleDescAttr(new_tupdesc, new_attno); + } + + /* Found it, check type and collation match */ + if (atttypid != att->atttypid || atttypmod != att->atttypmod) + elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", + attname, get_rel_name(to_rel)); + if (attcollation != att->attcollation) + elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", + attname, get_rel_name(to_rel)); + + vars = lappend(vars, makeVar(newvarno, + (AttrNumber) (new_attno + 1), + atttypid, + atttypmod, + attcollation, + 0)); + new_attno++; + } + + *translated_vars = vars; +} + +/* + * Adjust the targetlist entries of an inherited UPDATE operation + * + * The expressions have already been fixed, but we have to make sure that + * the target resnos match the child table (they may not, in the case of + * a column that was added after-the-fact by ALTER TABLE). In some cases + * this can force us to re-order the tlist to preserve resno ordering. + * (We do all this work in special cases so that preptlist.c is fast for + * the typical case.) + * + * The given tlist has already been through expression_tree_mutator; + * therefore the TargetEntry nodes are fresh copies that it's okay to + * scribble on. + * + * Note that this is not needed for INSERT because INSERT isn't inheritable. + */ +static List * +adjust_inherited_tlist(List *tlist, AppendRelInfo *context) +{ + bool changed_it = false; + ListCell *tl; + List *new_tlist; + bool more; + int attrno; + + /* This should only happen for an inheritance case, not UNION ALL */ + Assert(OidIsValid(context->parent_reloid)); + + /* Scan tlist and update resnos to match attnums of child rel */ + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + Var *childvar; + + if (tle->resjunk) + continue; /* ignore junk items */ + + /* Look up the translation of this column: it must be a Var */ + if (tle->resno <= 0 || + tle->resno > list_length(context->translated_vars)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + tle->resno, get_rel_name(context->parent_reloid)); + childvar = (Var *) list_nth(context->translated_vars, tle->resno - 1); + if (childvar == NULL || !IsA(childvar, Var)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + tle->resno, get_rel_name(context->parent_reloid)); + + if (tle->resno != childvar->varattno) + { + tle->resno = childvar->varattno; + changed_it = true; + } + } + + /* + * If we changed anything, re-sort the tlist by resno, and make sure + * resjunk entries have resnos above the last real resno. The sort + * algorithm is a bit stupid, but for such a seldom-taken path, small is + * probably better than fast. + */ + if (!changed_it) + return tlist; + + new_tlist = NIL; + more = true; + for (attrno = 1; more; attrno++) + { + more = false; + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + + if (tle->resjunk) + continue; /* ignore junk items */ + + if (tle->resno == attrno) + new_tlist = lappend(new_tlist, tle); + else if (tle->resno > attrno) + more = true; + } + } + + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + + if (!tle->resjunk) + continue; /* here, ignore non-junk items */ + + tle->resno = attrno; + new_tlist = lappend(new_tlist, tle); + attrno++; + } + + return new_tlist; +} diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index d50d86b252..8ce88876c4 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -20,6 +20,7 @@ #include "foreign/fdwapi.h" #include "nodes/extensible.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index e7b4965337..12d8feb409 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -106,7 +106,7 @@ static void set_baserel_partition_key_exprs(Relation relation, */ void get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) + Bitmapset *updatedCols, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; @@ -445,11 +445,32 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, get_relation_foreign_keys(root, rel, relation, inhparent); /* - * Collect info about relation's partitioning scheme, if any. Only - * inheritance parents may be partitioned. + * Collect some additional information for inheritance parents. */ - if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - set_relation_partition_info(root, rel, relation); + if (inhparent) + { + /* + * We'll need the TupleDesc when initializing the child relation. + * A copy is being made because concurrent changes might drop + * the relcache entry. That's possible because ALTER TABLE + * child_table NO INHERIT parent_table only requires an + * AccessShareLock on parent_table. + */ + rel->tupdesc = CreateTupleDescCopy(RelationGetDescr(relation)); + rel->reltype = RelationGetForm(relation)->reltype; + + /* + * If partitioned, also save the information of partitioning scheme, + * and whether the query updates any of the partition key columns. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + set_relation_partition_info(root, rel, relation); + if (!root->partColsUpdated) + root->partColsUpdated = + has_partition_attrs(relation, updatedCols, NULL); + } + } heap_close(relation, NoLock); @@ -1855,16 +1876,20 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; + + /* + * Since we must've taken a lock on the table, it's okay to simply copy + * the pointers to relcache data here. + */ + rel->part_oids = partdesc->oids; + rel->boundinfo = partdesc->boundinfo; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 29ba19349f..52a11f434f 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -17,6 +17,7 @@ #include <limits.h> #include "miscadmin.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" @@ -130,6 +131,37 @@ setup_append_rel_array(PlannerInfo *root) } } +/* Expand the PlannerInfo arrays by add_size members and zero-init it. */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size = root->simple_rel_array_size + add_size; + + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + if (root->append_rel_array) + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + else + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + + /* Set the contents of just allocated memory to 0. */ + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + root->simple_rel_array_size = new_size; +} + /* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. @@ -238,7 +270,8 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte->relid, rte->inh, rte->updatedCols, + rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -279,52 +312,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); - /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. - */ - if (rte->inh) - { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); - - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; - } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); - } - return rel; } diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index d6ca03de4a..9120fcb0d3 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -44,7 +44,9 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/planner.h" #include "optimizer/predtest.h" @@ -436,17 +438,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -540,23 +548,20 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ @@ -564,6 +569,13 @@ prune_append_rel_partitions(RelOptInfo *rel) return NULL; /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. */ @@ -590,15 +602,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 06212ce382..572efff742 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -15,6 +15,7 @@ #define RELATION_H #include "access/sdir.h" +#include "access/tupdesc.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/params.h" @@ -711,6 +712,7 @@ typedef struct RelOptInfo int nparts; /* number of partitions */ struct PartitionBoundInfoData *boundinfo; /* Partition bounds */ List *partition_qual; /* partition constraint */ + Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ List **partexprs; /* Non-nullable partition key expressions. */ @@ -720,6 +722,12 @@ typedef struct RelOptInfo Index inh_root_parent; /* For otherrels, this is the RT index of * inheritance table mentioned in the query * from which this relation originated */ + /* + * Set only if this is an inheritance parent relation. This information + * is needed when initializing the planning info for children. + */ + TupleDesc tupdesc; /* A "copy" of the table's tuple desriptor */ + Oid reltype; /* Table's reltype */ } RelOptInfo; /* diff --git a/src/include/optimizer/append.h b/src/include/optimizer/append.h new file mode 100644 index 0000000000..5b44530279 --- /dev/null +++ b/src/include/optimizer/append.h @@ -0,0 +1,28 @@ +/*------------------------------------------------------------------------- + * + * append.h + * prototypes for append.c. + * + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/optimizer/append.h + * + *------------------------------------------------------------------------- + */ +#ifndef APPEND_H +#define APPEND_H + +#include "nodes/relation.h" + +/* + * append.c + * utilities for dealing with append relations + */ +extern void expand_append_rel(PlannerInfo *root, + RelOptInfo *rel, + RangeTblEntry *rte, + Index rti); + +#endif /* APPEND_H */ diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h new file mode 100644 index 0000000000..e205e78e6d --- /dev/null +++ b/src/include/optimizer/appendinfo.h @@ -0,0 +1,43 @@ +/*------------------------------------------------------------------------- + * + * appendinfo.h + * Routines for mapping expressions between append rel parent(s) and + * children + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/optimizer/appendinfo.h + * + *------------------------------------------------------------------------- + */ +#ifndef APPENDINFO_H +#define APPENDINFO_H + +#include "nodes/plannodes.h" +#include "nodes/relation.h" +#include "utils/relcache.h" + +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parentrel, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex); +extern Bitmapset *translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars); +extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, + int nappinfos, AppendRelInfo **appinfos); + +extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, + Relids child_relids, + Relids top_parent_relids); + +extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root, + Relids relids, int *nappinfos); + +extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, + SpecialJoinInfo *parent_sjinfo, + Relids left_relids, Relids right_relids); +extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, + Relids child_relids, Relids top_parent_relids); + +#endif /* APPENDINFO_H */ diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 81abcf53a8..b1baa3117a 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -261,6 +261,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index 7d53cbbb87..edaf2a3b4f 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -26,7 +26,7 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); + bool inhparent, Bitmapset *updatedCols, RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 38608770a2..0e2cdbc539 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -38,7 +38,7 @@ extern Expr *canonicalize_qual(Expr *qual, bool is_check); /* * prototypes for preptlist.c */ -extern List *preprocess_targetlist(PlannerInfo *root); +extern List *preprocess_targetlist(PlannerInfo *root, bool inheritance_update); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); @@ -47,22 +47,4 @@ extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); */ extern RelOptInfo *plan_set_operations(PlannerInfo *root); -extern void expand_inherited_tables(PlannerInfo *root); - -extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, - int nappinfos, AppendRelInfo **appinfos); - -extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, - Relids child_relids, - Relids top_parent_relids); - -extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root, - Relids relids, int *nappinfos); - -extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, - SpecialJoinInfo *parent_sjinfo, - Relids left_relids, Relids right_relids); -extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, - Relids child_relids, Relids top_parent_relids); - #endif /* PREP_H */ diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h index e07aaaf798..ac0a979010 100644 --- a/src/include/partitioning/partprune.h +++ b/src/include/partitioning/partprune.h @@ -76,7 +76,7 @@ extern PartitionPruneInfo *make_partition_pruneinfo(PlannerInfo *root, List *subpaths, List *partitioned_rels, List *prunequal); -extern Relids prune_append_rel_partitions(RelOptInfo *rel); +extern Bitmapset *prune_append_rel_partitions(RelOptInfo *rel); extern Bitmapset *get_matching_partitions(PartitionPruneContext *context, List *pruning_steps); diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 1f5378080d..73f684f992 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -5547,29 +5547,29 @@ select t1.b, ss.phv from join_ut1 t1 left join lateral (select t2.a as t2a, t3.a t3a, least(t1.a, t2.a, t3.a) phv from join_pt1 t2 join join_ut1 t3 on t2.a = t3.b) ss on t1.a = ss.t2a order by t1.a; - QUERY PLAN ------------------------------------------------------------------- + QUERY PLAN +-------------------------------------------------------------------- Sort - Output: t1.b, (LEAST(t1.a, t2.a, t3.a)), t1.a + Output: t1.b, (LEAST(t1.a, t2_1.a, t3.a)), t1.a Sort Key: t1.a -> Nested Loop Left Join - Output: t1.b, (LEAST(t1.a, t2.a, t3.a)), t1.a + Output: t1.b, (LEAST(t1.a, t2_1.a, t3.a)), t1.a -> Seq Scan on public.join_ut1 t1 Output: t1.a, t1.b, t1.c -> Hash Join - Output: t2.a, LEAST(t1.a, t2.a, t3.a) - Hash Cond: (t3.b = t2.a) + Output: t2_1.a, LEAST(t1.a, t2_1.a, t3.a) + Hash Cond: (t3.b = t2_1.a) -> Seq Scan on public.join_ut1 t3 Output: t3.a, t3.b, t3.c -> Hash - Output: t2.a + Output: t2_1.a -> Append - -> Seq Scan on public.join_pt1p1p1 t2 - Output: t2.a - Filter: (t1.a = t2.a) - -> Seq Scan on public.join_pt1p2 t2_1 + -> Seq Scan on public.join_pt1p1p1 t2_1 Output: t2_1.a Filter: (t1.a = t2_1.a) + -> Seq Scan on public.join_pt1p2 t2 + Output: t2.a + Filter: (t1.a = t2.a) (21 rows) select t1.b, ss.phv from join_ut1 t1 left join lateral diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 6bc106831e..1450cef057 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) -- 2.11.0
0005-Teach-planner-to-only-process-unpruned-partitions.patch (text/plain, 7.1K) download From 0a4bc18f85831e43bc572c42d33aaf1869861ae8 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 15 Oct 2018 10:59:24 +0900 Subject: [PATCH 5/6] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/append.c | 8 ++++++++ src/backend/optimizer/util/relnode.c | 3 +++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/relation.h | 2 ++ 6 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 6e321ec9e7..76531a05cf 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1424,6 +1424,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index b54dc6c38e..6950636b3e 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6766,7 +6766,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -6774,9 +6776,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -6857,7 +6857,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -6869,7 +6868,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -6879,9 +6880,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/append.c b/src/backend/optimizer/util/append.c index 76d9246d5f..4202a4e00d 100644 --- a/src/backend/optimizer/util/append.c +++ b/src/backend/optimizer/util/append.c @@ -166,6 +166,7 @@ expand_append_rel(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, /* Perform pruning. */ partindexes = prune_append_rel_partitions(rel); + rel->live_parts = partindexes; /* Must expand PlannerInfo arrays before we can add children. */ expand_planner_arrays(root, bms_num_members(partindexes)); @@ -192,6 +193,13 @@ expand_append_rel(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, NoLock, rootrc); rel->part_rels[i] = childrel; + + /* + * If partition is excluded by constraints, remove it from + * live_parts, too. + */ + if (childrel && IS_DUMMY_REL(childrel)) + rel->live_parts = bms_del_member(rel->live_parts, i); } /* diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 52a11f434f..6c7eafe6db 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1748,6 +1748,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 9120fcb0d3..2ed5c9e8dd 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -441,29 +441,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 572efff742..4a2e096e84 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -715,6 +715,8 @@ typedef struct RelOptInfo Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Set of live partitions; contains indexes + * into part_rels array */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.11.0
0006-Do-not-lock-all-partitions-at-the-beginning.patch (text/plain, 1.3K) download From 90d20b1adf71ae3a335687d97fe7c51426095968 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 12 Oct 2018 19:12:53 +0900 Subject: [PATCH 6/6] Do not lock all partitions at the beginning --- src/backend/optimizer/util/append.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/backend/optimizer/util/append.c b/src/backend/optimizer/util/append.c index 4202a4e00d..4080dea041 100644 --- a/src/backend/optimizer/util/append.c +++ b/src/backend/optimizer/util/append.c @@ -150,10 +150,6 @@ expand_append_rel(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, Assert(rte->rtekind == RTE_RELATION); Assert(lockmode != NoLock); - /* If root partitioned table, lock *all* partitions in the tree. */ - if (rel->relid == root_parent) - (void) find_all_inheritors(rte->relid, lockmode, NULL); - /* * Initialize partitioned_child_rels to contain this RT index. * @@ -187,10 +183,9 @@ expand_append_rel(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, Oid childOID = rel->part_oids[i]; RelOptInfo *childrel; - /* Already locked, so pass NoLock. */ childrel = add_inheritance_child_rel(root, rel, rte, childOID, - NoLock, + lockmode, rootrc); rel->part_rels[i] = childrel; -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2018-11-05 08:28  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 2 replies; 265+ messages in thread

From: Imai, Yoshikazu @ 2018-11-05 08:28 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers; David Rowley <david.rowley@2ndquadrant.com>

Hi,

On Thu, Oct 25, 2018 at 10:38 PM, Amit Langote wrote:
> And here is the latest set of patches.  Sorry it took me a while.

Thanks for revising the patch!

> I didn't get time today to repeat the performance tests, but I'm planning
> to next week.  In the meantime, please feel free to test them and provide
> comments on the code changes.

Since it takes me a lot of time to see all of the patches, I will post comments
little by little from easy parts like typo check.


1.
0002:
+ * inheritance_make_rel_from_joinlist
+ *		Perform join planning for all non-dummy leaf inheritance chilren
+ *		in their role as query's target relation

"inheritance chilren" -> "inheritance children"


2.
0002:
+		/*
+		 * Sub-partitions have to be processed recursively, because
+		 * AppendRelInfoss link sub-partitions to their immediate parents, not
+		 * the root partitioned table.
+		 */

AppendRelInfoss -> AppendRelInfos (?)


3.
0002:
+		/* Reset interal join planning data structures. */

interal -> internal


4.
0004:
-static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte,
-						 Index rti);

Comments referring to expand_inherited_rtentry() is left.

backend/optimizer/plan/planner.c:1310:
           * Because of the way expand_inherited_rtentry works, that should be
backend/optimizer/plan/planner.c:1317:
           * Instead the duplicate child RTE created by expand_inherited_rtentry
backend/optimizer/util/plancat.c:118:
    * the rewriter or when expand_inherited_rtentry() added it to the query's
backend/optimizer/util/plancat.c:640:
    * the rewriter or when expand_inherited_rtentry() added it to the query's

About the last two comments in the above,
"the rewriter or when expand_inherited_rtentry() added it to the query's"
would be
"the rewriter or when add_inheritance_child_rel() added it to the query's".

I don't know how to correct the first two comments.


5.
0004:
-static void expand_partitioned_rtentry(PlannerInfo *root,
-						   RangeTblEntry *parentrte,
-						   Index parentRTindex, Relation parentrel,
-						   PlanRowMark *top_parentrc, LOCKMODE lockmode,
-						   List **appinfos);

Comments referring to expand_partitioned_rtentry() is also left.

backend/executor/execPartition.c:941:
 /*
  * get_partition_dispatch_recurse
  *      Recursively expand partition tree rooted at rel
  *
  * As the partition tree is expanded in a depth-first manner, we maintain two
  * global lists: of PartitionDispatch objects corresponding to partitioned
  * tables in *pds and of the leaf partition OIDs in *leaf_part_oids.
  *
  * Note that the order of OIDs of leaf partitions in leaf_part_oids matches
  * the order in which the planner's expand_partitioned_rtentry() processes
  * them.  It's not necessarily the case that the offsets match up exactly,
  * because constraint exclusion might prune away some partitions on the
  * planner side, whereas we'll always have the complete list; but unpruned
  * partitions will appear in the same order in the plan as they are returned
  * here.
  */

I think the second paragraph of the comments is no longer correct.
expand_partitioned_rtentry() expands the partition tree in a depth-first
manner, whereas expand_append_rel() doesn't neither in a depth-first manner
nor a breadth-first manner as below.

partitioned table tree image:
pt
  sub1
    sub1_1
      leaf0
    leaf1
  sub2
    leaf2
  leaf3

append_rel_list(expanded by expand_partitioned_rtentry):
  [sub1, sub1_1, leaf0, leaf1, sub2, leaf2, leaf3]

append_rel_list(expanded by expand_append_rel):
  [sub1, sub2, leaf3, sub1_1, leaf1, leaf0, leaf2]


6.
0004
+	/*
+	 * A partitioned child table with 0 children is a dummy rel, so don't
+	 * bother creating planner objects for it.
+	 */
+	if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionDesc partdesc = RelationGetPartitionDesc(childrel);
+
+		Assert(!RELATION_IS_OTHER_TEMP(childrel));
+		if (partdesc->nparts == 0)
+		{
+			heap_close(childrel, NoLock);
+			return NULL;
+		}
+	}
+
+	/*
+	 * If childrel doesn't belong to this session, skip it, also relinquishing
+	 * the lock.
+	 */
+	if (RELATION_IS_OTHER_TEMP(childrel))
+	{
+		heap_close(childrel, lockmode);
+		return NULL;
+	}

If we process the latter if block before the former one, Assert can be excluded
from the code.



It might be difficult to me to examine the codes whether there exists any logical
wrongness along with significant planner code changes, but I will try to look it.
Since it passes make check-world successfully, I think as of now there might be
not any wrong points.

If there's time, I will also do the performance tests.


--
Yoshikazu Imai 



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2018-11-07 01:00  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  1 sibling, 1 reply; 265+ messages in thread

From: Imai, Yoshikazu @ 2018-11-07 01:00 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers; David Rowley <david.rowley@2ndquadrant.com>

About inheritance_make_rel_from_joinlist(), I considered how it processes
joins for sub-partitioned-table.

sub-partitioned-table image:
part
  sub1
    leaf1
    leaf2

inheritance_make_rel_from_joinlist() translates join_list and join_info_list
for each leafs(leaf1, leaf2 in above image). To translate those two lists for
leaf1, inheritance_make_rel_from_joinlist() translates lists from part to sub1
and nextly from sub1 to leaf1. For leaf2, inheritance_make_rel_from_joinlist() 
translates lists from part to sub1 and from sub1 to leaf2. There are same
translation from part to sub1, and I think it is better if we can eliminate it.
I attached 0002-delta.patch.

In the patch, inheritance_make_rel_from_joinlist() translates lists not only for
leafs but for mid-nodes, in a depth-first manner, so it can use lists of
mid-nodes for translating lists from mid-node to leafs, which eliminates same
translation.

I think it might be better if we can apply same logic to inheritance_planner(),
which once implemented the same logic as below. 


    foreach(lc, root->append_rel_list)
    {
        AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
        ...
        /*
         * expand_inherited_rtentry() always processes a parent before any of
         * that parent's children, so the parent_root for this relation should
         * already be available.
         */
        parent_root = parent_roots[appinfo->parent_relid];
        Assert(parent_root != NULL);
        parent_parse = parent_root->parse;
        ...
        subroot->parse = (Query *)
            adjust_appendrel_attrs(parent_root,
                                   (Node *) parent_parse,
                                   1, &appinfo);


--
Yoshikazu Imai



Attachments:

0002-delta.patch (application/octet-stream, 2.5K) download diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 971da18..ab5c02d 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -2593,8 +2593,6 @@ inheritance_make_rel_from_joinlist(PlannerInfo *root, RelOptInfo *childrel; AppendRelInfo *appinfo = lfirst(lc); List *translated_joinlist; - Relids child_relids, - top_parent_relids; /* Only consider this parent's children. */ if (appinfo->parent_relid != parent->relid) @@ -2608,36 +2606,35 @@ inheritance_make_rel_from_joinlist(PlannerInfo *root, continue; /* + * Modify joinlist such that relations joined to the top parent rel + * appear to be joined to the child rel instead. Do the same for + * any SpecialJoinInfo structs. + */ + Assert(childrel->inh_root_parent > 0); + translated_joinlist = (List *) + adjust_appendrel_attrs(root, (Node *) joinlist, + 1, &appinfo); + + root->join_info_list = (List *) + adjust_appendrel_attrs(root, (Node *) join_info_list, + 1, &appinfo); + + /* * Sub-partitions have to be processed recursively, because * AppendRelInfoss link sub-partitions to their immediate parents, not * the root partitioned table. */ if (childrel->part_scheme != NULL) { - inheritance_make_rel_from_joinlist(root, childrel, joinlist); + inheritance_make_rel_from_joinlist(root, childrel, + translated_joinlist); + + /* Restore the original for processing the next child. */ + root->join_info_list = join_info_list; continue; } /* - * Modify joinlist such that relations joined to the top parent rel - * appear to be joined to the child rel instead. Do the same for - * any SpecialJoinInfo structs. - */ - child_relids = bms_make_singleton(appinfo->child_relid); - Assert(childrel->inh_root_parent > 0); - top_parent_relids = bms_make_singleton(childrel->inh_root_parent); - translated_joinlist = (List *) - adjust_appendrel_attrs_multilevel(root, - (Node *) joinlist, - child_relids, - top_parent_relids); - root->join_info_list = (List *) - adjust_appendrel_attrs_multilevel(root, - (Node *) join_info_list, - child_relids, - top_parent_relids); - - /* * Since we added the child rel directly into the join tree, we must * modify it to be a "base" rel instead of an "other" rel, which the * join planning code expects the relations being joined to be.
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-11-07 03:32  Alvaro Herrera <alvherre@2ndquadrant.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 1 reply; 265+ messages in thread

From: Alvaro Herrera @ 2018-11-07 03:32 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers; David Rowley <david.rowley@2ndquadrant.com>

ISTM patch 0004 is impossible to review just because of size -- I
suppose the bulk of it is just code that moves from one file to another,
but that there are also code changes in it.  Maybe it would be better to
split it so that one patch moves the code to the new files without
changing it, then another patch does the actual functional changes?  If
git shows the first half as a rename, it's easier to be confident about
it.

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-11-07 03:44  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Alvaro Herrera <alvherre@2ndquadrant.com>
  0 siblings, 1 reply; 265+ messages in thread

From: Amit Langote @ 2018-11-07 03:44 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers; David Rowley <david.rowley@2ndquadrant.com>

Thanks for looking.

On 2018/11/07 12:32, Alvaro Herrera wrote:
> ISTM patch 0004 is impossible to review just because of size -- I
> suppose the bulk of it is just code that moves from one file to another,
> but that there are also code changes in it.  Maybe it would be better to
> split it so that one patch moves the code to the new files without
> changing it, then another patch does the actual functional changes?  If
> git shows the first half as a rename, it's easier to be confident about
> it.

Okay, will post a new version structured that way shortly.

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-11-09 08:04  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  1 sibling, 0 replies; 265+ messages in thread

From: Amit Langote @ 2018-11-09 08:04 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: pgsql-hackers; David Rowley <david.rowley@2ndquadrant.com>

Imai-san,

Thank you for reviewing.

On 2018/11/05 17:28, Imai, Yoshikazu wrote:
> Since it takes me a lot of time to see all of the patches, I will post comments
> little by little from easy parts like typo check.

I've fixed the typos you pointed out.

> 4.
> 0004:
> -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte,
> -						 Index rti);
> 
> Comments referring to expand_inherited_rtentry() is left.
> 
> backend/optimizer/plan/planner.c:1310:
>            * Because of the way expand_inherited_rtentry works, that should be
> backend/optimizer/plan/planner.c:1317:
>            * Instead the duplicate child RTE created by expand_inherited_rtentry
> backend/optimizer/util/plancat.c:118:
>     * the rewriter or when expand_inherited_rtentry() added it to the query's
> backend/optimizer/util/plancat.c:640:
>     * the rewriter or when expand_inherited_rtentry() added it to the query's
> 
> About the last two comments in the above,
> "the rewriter or when expand_inherited_rtentry() added it to the query's"
> would be
> "the rewriter or when add_inheritance_child_rel() added it to the query's".
> 
> I don't know how to correct the first two comments.

I've since modified the patch to preserve expand_inherited_rtentry name,
although with a new implementation.

> 5.
> 0004:
> -static void expand_partitioned_rtentry(PlannerInfo *root,
> -						   RangeTblEntry *parentrte,
> -						   Index parentRTindex, Relation parentrel,
> -						   PlanRowMark *top_parentrc, LOCKMODE lockmode,
> -						   List **appinfos);
> 
> Comments referring to expand_partitioned_rtentry() is also left.

I've preserved this name as well.

> backend/executor/execPartition.c:941:
>  /*
>   * get_partition_dispatch_recurse
>   *      Recursively expand partition tree rooted at rel
>   *
>   * As the partition tree is expanded in a depth-first manner, we maintain two
>   * global lists: of PartitionDispatch objects corresponding to partitioned
>   * tables in *pds and of the leaf partition OIDs in *leaf_part_oids.
>   *
>   * Note that the order of OIDs of leaf partitions in leaf_part_oids matches
>   * the order in which the planner's expand_partitioned_rtentry() processes
>   * them.  It's not necessarily the case that the offsets match up exactly,
>   * because constraint exclusion might prune away some partitions on the
>   * planner side, whereas we'll always have the complete list; but unpruned
>   * partitions will appear in the same order in the plan as they are returned
>   * here.
>   */
> 
> I think the second paragraph of the comments is no longer correct.
> expand_partitioned_rtentry() expands the partition tree in a depth-first
> manner, whereas expand_append_rel() doesn't neither in a depth-first manner
> nor a breadth-first manner as below.

Well, expand_append_rel doesn't process a whole partitioning tree on its
own, only one partitioned table at a time.  As set_append_rel_size()
traverses the root->append_rel_list, leaf partitions get added to the
resulting plan in what's effectively a depth-first order.

I've updated this comment as far this patch is concerned, although the
patch in the other thread about removal of executor overhead in
partitioning is trying to remove this function
(get_partition_dispatch_recurse) altogether anyway.

> 6.
> 0004
> +	/*
> +	 * A partitioned child table with 0 children is a dummy rel, so don't
> +	 * bother creating planner objects for it.
> +	 */
> +	if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
> +	{
> +		PartitionDesc partdesc = RelationGetPartitionDesc(childrel);
> +
> +		Assert(!RELATION_IS_OTHER_TEMP(childrel));
> +		if (partdesc->nparts == 0)
> +		{
> +			heap_close(childrel, NoLock);
> +			return NULL;
> +		}
> +	}
> +
> +	/*
> +	 * If childrel doesn't belong to this session, skip it, also relinquishing
> +	 * the lock.
> +	 */
> +	if (RELATION_IS_OTHER_TEMP(childrel))
> +	{
> +		heap_close(childrel, lockmode);
> +		return NULL;
> +	}
> 
> If we process the latter if block before the former one, Assert can be excluded
> from the code.

I've since moved the partitioning-related code to a partitioning specific
function, so this no longer applies.

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-11-09 08:26  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  0 siblings, 1 reply; 265+ messages in thread

From: Amit Langote @ 2018-11-09 08:26 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: pgsql-hackers; David Rowley <david.rowley@2ndquadrant.com>

Imai-san,

On 2018/11/07 10:00, Imai, Yoshikazu wrote:
> About inheritance_make_rel_from_joinlist(), I considered how it processes
> joins for sub-partitioned-table.
> 
> sub-partitioned-table image:
> part
>   sub1
>     leaf1
>     leaf2
> 
> inheritance_make_rel_from_joinlist() translates join_list and join_info_list
> for each leafs(leaf1, leaf2 in above image). To translate those two lists for
> leaf1, inheritance_make_rel_from_joinlist() translates lists from part to sub1
> and nextly from sub1 to leaf1. For leaf2, inheritance_make_rel_from_joinlist() 
> translates lists from part to sub1 and from sub1 to leaf2. There are same
> translation from part to sub1, and I think it is better if we can eliminate it.
> I attached 0002-delta.patch.
> 
> In the patch, inheritance_make_rel_from_joinlist() translates lists not only for
> leafs but for mid-nodes, in a depth-first manner, so it can use lists of
> mid-nodes for translating lists from mid-node to leafs, which eliminates same
> translation.

I don't think the translation happens twice for the same leaf partitions.

Applying adjust_appendrel_attrs_*multilevel* for only leaf nodes, as
happens with the current code, is same as first translating using
adjust_appendrel_attrs from part to sub1 and then from sub1 to leaf1 and
leaf2 during recursion with sub1 as the parent.

> I think it might be better if we can apply same logic to inheritance_planner(),
> which once implemented the same logic as below. 
> 
> 
>     foreach(lc, root->append_rel_list)
>     {
>         AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
>         ...
>         /*
>          * expand_inherited_rtentry() always processes a parent before any of
>          * that parent's children, so the parent_root for this relation should
>          * already be available.
>          */
>         parent_root = parent_roots[appinfo->parent_relid];
>         Assert(parent_root != NULL);
>         parent_parse = parent_root->parse;
>         ...
>         subroot->parse = (Query *)
>             adjust_appendrel_attrs(parent_root,
>                                    (Node *) parent_parse,
>                                    1, &appinfo);

Actually, inheritance_planner is also using
adjust_appendrel_attrs_multilevel.  I'm not sure if you're looking at the
latest (10/26) patch.

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-11-09 08:55  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 2 replies; 265+ messages in thread

From: Amit Langote @ 2018-11-09 08:55 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers; David Rowley <david.rowley@2ndquadrant.com>

On 2018/11/07 12:44, Amit Langote wrote:
> Thanks for looking.
> 
> On 2018/11/07 12:32, Alvaro Herrera wrote:
>> ISTM patch 0004 is impossible to review just because of size -- I
>> suppose the bulk of it is just code that moves from one file to another,
>> but that there are also code changes in it.  Maybe it would be better to
>> split it so that one patch moves the code to the new files without
>> changing it, then another patch does the actual functional changes?  If
>> git shows the first half as a rename, it's easier to be confident about
>> it.
> 
> Okay, will post a new version structured that way shortly.

And here are patches structured that way.  I've addressed some of the
comments posted by Imai-san upthread.  Also, since David's patch to
postpone PlannerInfo.total_pages calculation went into the tree earlier
this week, it's no longer included in this set.

With this breakdown, patches are as follows:

v5-0001-Store-inheritance-root-parent-index-in-otherrel-s.patch

  Adds a inh_root_parent field that's set in inheritance child otherrel
  RelOptInfos to store the RT index of their root parent

v5-0002-Overhaul-inheritance-update-delete-planning.patch

  Patch that adjusts planner so that inheritance_planner can use partition
  pruning.

v5-0003-Lazy-creation-of-RTEs-for-inheritance-children.patch

  Patch that adjusts planner such that inheritance expansion step is
  postponed from early subquery_planner to the beginning of
  set_append_rel_size, so that child tables are added to the Query and
  PlannerInfo after partition pruning.  While the move only benefits
  partitioning, because non-partition children cannot be pruned until
  after they're fully opened, the new code handles both partitioned tables
  and regular inheritance parents.

v5-0004-Move-append-expansion-code-into-its-own-file.patch

  With patch 0003, inheritance expansion is no longer a part of the prep
  phase of planning, so it seems odd that inheritance expansion code is in
  optimizer/prep/prepunion.c.  This patch moves the code to a new file
  optimizer/utils/append.c.  Also, some of the appendrel translation
  subroutines are moved over to optimizer/utils/appendinfo.c.

  No functional changes with this patch.

v5-0005-Teach-planner-to-only-process-unpruned-partitions.patch

  Patch that adds a live_parts field to RelOptInfo which is set in
  partitioned rels to contain partition indexes of only non-dummy children
  replace the loops of the following form:

    for (i = 0; i < rel->nparts; i++)
    {
        RelOptInfo *partrel = rel->part_rels[i];

        ...some processing
    }

  at various points within the planner with:

    i = -1
    while ((i = bms_get_next(rel->live_parts)) >= 0)
    {
        RelOptInfo *partrel = rel->part_rels[i];

        ...some processing
    }

v5-0006-Do-not-lock-all-partitions-at-the-beginning.patch

  A simple patch that removes the find_all_inheritors called for
  partitioned root parent only to lock *all* partitions, in favor of
  locking only the unpruned partitions.

Thanks,
Amit

Attachments: download all (6 files, .tar.gz)

v5-0001-Store-inheritance-root-parent-index-in-otherrel-s.patch (text/plain, 2.5K) download From 0f66b82a6ab998c4d790a4e2a6437950edf7de18 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 26 Oct 2018 16:45:59 +0900 Subject: [PATCH v5 1/6] Store inheritance root parent index in otherrel's RelOptInfo Although it's set by build_simple_rel, it's not being used by any code yet. --- src/backend/nodes/outfuncs.c | 1 + src/backend/optimizer/util/relnode.c | 14 ++++++++++++++ src/include/nodes/relation.h | 4 ++++ 3 files changed, 19 insertions(+) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 69731ccdea..53a657c0ae 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2371,6 +2371,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_BOOL_FIELD(consider_partitionwise_join); WRITE_BITMAPSET_FIELD(top_parent_relids); WRITE_NODE_FIELD(partitioned_child_rels); + WRITE_UINT_FIELD(inh_root_parent); } static void diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 39f5729b91..29ba19349f 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -215,9 +215,23 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->top_parent_relids = parent->top_parent_relids; else rel->top_parent_relids = bms_copy(parent->relids); + + /* + * For inheritance child relations, we also set inh_root_parent. + * Note that 'parent' might itself be a child (a sub-partitioned + * partition), in which case we simply use its value of + * inh_root_parent. + */ + if (parent->rtekind == RTE_RELATION) + rel->inh_root_parent = parent->inh_root_parent > 0 ? + parent->inh_root_parent : + parent->relid; } else + { rel->top_parent_relids = NULL; + rel->inh_root_parent = 0; + } /* Check type of rtable entry */ switch (rte->rtekind) diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 6fd24203dd..32ac3315a4 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -703,6 +703,10 @@ typedef struct RelOptInfo List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ + + Index inh_root_parent; /* For otherrels, this is the RT index of + * inheritance table mentioned in the query + * from which this relation originated */ } RelOptInfo; /* -- 2.11.0
v5-0002-Overhaul-inheritance-update-delete-planning.patch (text/plain, 44.9K) download From f14e09f119ce85c344b581af6b1623982d7cc2b1 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 11:18:12 +0900 Subject: [PATCH v5 2/6] Overhaul inheritance update/delete planning With the current method, inheritance_planner, which handles the planning for update/delete commands targeting inheritance trees, performs the *whole* planning for each child table. That involves translating the query tree to set a given child table in place of the original inheritance root table as the query's target table, followed by calling grouping_planner on the translated query. That's inefficient for two reasons: 1. partprune.c cannot be used to perform partition pruning, because it can only be invoked from query_planner. With the current approach, query_planner only sees the individual partitions in the query tree it receives, not the parent partitioned table. This leaves each partition to be pruned using constraint exclusion. 2. Repeated invocation of query_planner results in allocating large amounts of memory, especially if there are many child tables. Also, it involves repeatedly performing the same processing, such jointree processing. This commit addresses both of the issues by rewriting portions of inheritance_planner and adding some supporting functionality to allpath.c. With the new implementation, inheritance_planner calls query_planner only once at the beginning with the original unmodified query, which creates the access paths of individual relations after pruning any unnecessary partitions. If the query involves join against the target relation, join paths are created for each target child relation by replacing the original target table in the join tree by a given child table. Join relations (RelOptInfos thereof) for all target child relations are collected in a global list in PlannerInfo. After creating the join paths for all target child relations, it calls grouping_planner() on each child join relation to finish up the paths such that they produce query's top-level target list expanded per a given child relation's descriptor. grouping_planner()'s interface is modified so that we can pass it what's called a 'planned_rel', a RelOptInfo that already contains the necessary paths needed to produce its output. This removes some existing code in inheritance_planner that dealt with any subquery RTEs in the query. The rationale of that code was that the subquery RTEs may change during each iteration of planning (that is, for different children), so different iterations better use different copies of those RTEs. That was handled by making fresh copies of those RTEs for each iteration of planning which were appended to the range table, accompanied by modifying all expressions and auxiliary structures that referenced the original subquery RTEs to instead reference the copies (that is, change the varnos). This copying meant we would end up adding S * N new entries to the original range table by the time we got to the last unpruned child, where S is the number of subquery RTEs in the original query and N the number of unpruned children. Since with the new code we perform planning just once, I think we don't need this special handling. Actually, there is a regression test output change due to no longer having copies of subquery RTEs (see the diff of partition_join.out file.) Also we no longer need the hack in relation_excluded_by_constraints() involving setting PlannerInfo.inhTargetKind to enable constraint exclusion for target child relation is no longer needed. This commit removes inheritance_planner in favor of rearranging things such that, only the planning steps that affect 1 and 2 above are repeated for unpruned child relations. --- doc/src/sgml/ddl.sgml | 14 +- src/backend/nodes/outfuncs.c | 1 - src/backend/optimizer/path/allpaths.c | 212 +++++++++++++- src/backend/optimizer/plan/createplan.c | 10 - src/backend/optimizer/plan/planner.c | 400 +++++++-------------------- src/backend/optimizer/prep/prepjointree.c | 1 - src/backend/optimizer/prep/prepunion.c | 33 ++- src/backend/optimizer/util/plancat.c | 50 +--- src/include/nodes/relation.h | 18 +- src/test/regress/expected/partition_join.out | 4 +- 10 files changed, 356 insertions(+), 387 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index b5ed1b7939..8cb69b1a47 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -3935,15 +3935,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <note> <para> - Currently, pruning of partitions during the planning of an - <command>UPDATE</command> or <command>DELETE</command> command is - implemented using the constraint exclusion method (however, it is - controlled by the <literal>enable_partition_pruning</literal> rather than - <literal>constraint_exclusion</literal>) &mdash; see the following section - for details and caveats that apply. - </para> - - <para> Execution-time partition pruning currently occurs for the <literal>Append</literal> and <literal>MergeAppend</literal> node types. </para> @@ -3964,9 +3955,8 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <para> <firstterm>Constraint exclusion</firstterm> is a query optimization - technique similar to partition pruning. While it is primarily used - for partitioning implemented using the legacy inheritance method, it can be - used for other purposes, including with declarative partitioning. + technique similar to partition pruning. It is primarily used + for partitioning implemented using the legacy inheritance method. </para> <para> diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 53a657c0ae..a95153fea7 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2309,7 +2309,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); WRITE_UINT_FIELD(qual_security_level); - WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind); WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasDeletedRTEs); diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 738bb30848..8de8d383f9 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -36,6 +36,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" @@ -119,6 +120,9 @@ static void set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); +static void inheritance_make_rel_from_joinlist(PlannerInfo *root, + RelOptInfo *parent, + List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, pushdown_safety_info *safetyInfo); static bool recurse_pushdown_safe(Node *setOp, Query *topquery, @@ -217,13 +221,28 @@ make_one_rel(PlannerInfo *root, List *joinlist) /* * Generate access paths for the entire join tree. + * + * For UPDATE/DELETE on an inheritance parent, join paths should be + * generated for each child result rel separately. */ - rel = make_rel_from_joinlist(root, joinlist); + if (root->parse->resultRelation && + root->simple_rte_array[root->parse->resultRelation]->inh) + { + rel = root->simple_rel_array[root->parse->resultRelation]; + inheritance_make_rel_from_joinlist(root, rel, joinlist); + Assert(list_length(root->inh_target_child_rels) == + list_length(root->inh_target_child_joinrels)); + } + else + { + rel = make_rel_from_joinlist(root, joinlist); - /* - * The result should join all and only the query's base rels. - */ - Assert(bms_equal(rel->relids, root->all_baserels)); + /* + * The result should join all and only the query's base rels. + */ + Assert(bms_equal(rel->relids, root->all_baserels)); + + } return rel; } @@ -1219,6 +1238,63 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, set_rel_consider_parallel(root, childrel, childRTE); /* + * If the root parent is the result relation, we need a reltarget + * for the child that will be suitable to use the child as + * target relation. + */ + if (childrel->inh_root_parent > 0 && + childrel->inh_root_parent == root->parse->resultRelation) + { + Query *parse, + *orig_parse = root->parse; + List *tlist; + List *other_exprs; + ListCell *lc2; + Relids child_relids, + top_parent_relids; + + /* + * We'd like to build the reltarget afresh; save the translated + * version of parent's expressions aside. + */ + other_exprs = childrel->reltarget->exprs; + childrel->reltarget->exprs = NIL; + + /* + * Translate the original query's expressions to this child, + * mainly for updating the childrel's reltarget so that it + * contains necessary expressions for using childrel as target + * relation correctly. We need to use multilevel translation + * as we can only access the original version of the query. + */ + child_relids = bms_make_singleton(appinfo->child_relid); + top_parent_relids = bms_make_singleton(childrel->inh_root_parent); + parse = (Query *) + adjust_appendrel_attrs_multilevel(root, + (Node *) root->parse, + child_relids, + top_parent_relids); + root->parse = parse; + tlist = preprocess_targetlist(root); + build_base_rel_tlists(root, tlist); + root->parse = orig_parse; + + /* + * Some of the expressions in parent's reltarget might not be + * in child's just built expressions. Check to add any that are + * missing. + */ + foreach(lc2, other_exprs) + { + Expr *expr = lfirst(lc2); + + if (!list_member(childrel->reltarget->exprs, expr)) + childrel->reltarget->exprs = + lappend(childrel->reltarget->exprs, expr); + } + } + + /* * Compute the child's size. */ set_rel_size(root, childrel, childRTindex, childRTE); @@ -2624,6 +2700,132 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows) } /* + * inheritance_make_rel_from_joinlist + * Perform join planning for all non-dummy leaf inheritance children + * in their role as query's target relation + * + * If a child relation is a partitioned table, its children are processed in + * turn by recursively calling this function. + */ +static void +inheritance_make_rel_from_joinlist(PlannerInfo *root, + RelOptInfo *parent, + List *joinlist) +{ + List *join_info_list = root->join_info_list; + ListCell *lc; +#ifdef USE_ASSERT_CHECKING + int top_parent_relid = parent->inh_root_parent > 0 ? + parent->inh_root_parent : + parent->relid; + Relids all_baserels; +#endif + + /* + * For UPDATE/DELETE queries, the top parent can only ever be a table. + * As a contrast, it could be a UNION ALL subquery in the case of SELECT. + */ + Assert(planner_rt_fetch(top_parent_relid, root)->rtekind == RTE_RELATION); + + /* Nothing to do. */ + if (IS_DUMMY_REL(parent)) + return; + + foreach(lc, root->append_rel_list) + { + RelOptInfo *childrel; + AppendRelInfo *appinfo = lfirst(lc); + List *translated_joinlist; + Relids child_relids, + top_parent_relids; + + /* Only consider this parent's children. */ + if (appinfo->parent_relid != parent->relid) + continue; + + childrel = find_base_rel(root, appinfo->child_relid); + + /* Ignore excluded/pruned children. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* + * Sub-partitions have to be processed recursively, because + * AppendRelInfos link sub-partitions to their immediate parents, not + * the root partitioned table. + */ + if (childrel->part_scheme != NULL) + { + inheritance_make_rel_from_joinlist(root, childrel, joinlist); + continue; + } + + /* + * Modify joinlist such that relations joined to the top parent rel + * appear to be joined to the child rel instead. Do the same for + * any SpecialJoinInfo structs. + */ + child_relids = bms_make_singleton(appinfo->child_relid); + Assert(childrel->inh_root_parent > 0); + top_parent_relids = bms_make_singleton(childrel->inh_root_parent); + translated_joinlist = (List *) + adjust_appendrel_attrs_multilevel(root, + (Node *) joinlist, + child_relids, + top_parent_relids); + root->join_info_list = (List *) + adjust_appendrel_attrs_multilevel(root, + (Node *) join_info_list, + child_relids, + top_parent_relids); + + /* + * Since we added the child rel directly into the join tree, we must + * modify it to be a "base" rel instead of an "other" rel, which the + * join planning code expects the relations being joined to be. + * + * NB: Do we need to change the child EC members to be marked + * as non-child somehow? + */ + childrel->reloptkind = RELOPT_BASEREL; + + /* Reset join planning specific data structures. */ + root->join_rel_list = NIL; + root->join_rel_hash = NULL; + + /* Perform join planning and save the resulting RelOptInfo. */ + childrel = make_rel_from_joinlist(root, translated_joinlist); + + /* + * Remember this child target rel. inheritance_planner will perform + * the remaining steps of planning for each child relation separately. + * Specifically, it will call grouping_planner on every + * RelOptInfo contained in the inh_target_child_joinrels list, each + * of which represents the source of tuples to be modified for a given + * target child rel. + */ + root->inh_target_child_rels = lappend_int(root->inh_target_child_rels, + appinfo->child_relid); + root->inh_target_child_joinrels = + lappend(root->inh_target_child_joinrels, childrel); + +#ifdef USE_ASSERT_CHECKING + /* + * The following implements essentially the same Assert as in + * make_one_rel, our caller. + */ + all_baserels = bms_copy(root->all_baserels); + all_baserels = bms_del_member(all_baserels, top_parent_relid); + all_baserels = bms_add_member(all_baserels, appinfo->child_relid); + Assert(bms_equal(childrel->relids, all_baserels)); +#endif + + /* Restore the original for processing the next child. */ + root->join_info_list = join_info_list; + } +} + +/* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. * diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index da7a92081a..28c4b53fea 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -1998,12 +1998,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) /* * During setrefs.c, we'll need the grouping_map to fix up the cols lists * in GroupingFunc nodes. Save it for setrefs.c to use. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be grouping in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->grouping_map == NULL); root->grouping_map = grouping_map; @@ -2160,12 +2155,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) * with InitPlan output params. (We can't just do that locally in the * MinMaxAgg node, because path nodes above here may have Agg references * as well.) Save the mmaggregates list to tell setrefs.c to do that. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be aggregates in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->minmax_aggs == NIL); root->minmax_aggs = best_path->mmaggregates; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index c729a99f8b..93b8c761c2 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -37,6 +37,7 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "nodes/relation.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" @@ -126,7 +127,7 @@ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static void inheritance_planner(PlannerInfo *root); static void grouping_planner(PlannerInfo *root, bool inheritance_update, - double tuple_fraction); + RelOptInfo *planned_rel, double tuple_fraction); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, int *tleref_to_colnum_map); @@ -630,7 +631,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->grouping_map = NULL; root->minmax_aggs = NIL; root->qual_security_level = 0; - root->inhTargetKind = INHKIND_NONE; root->hasRecursion = hasRecursion; if (hasRecursion) root->wt_param_id = SS_assign_special_param(root); @@ -970,7 +970,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh) inheritance_planner(root); else - grouping_planner(root, false, tuple_fraction); + grouping_planner(root, false, NULL, tuple_fraction); /* * Capture the set of outer-level param IDs we have access to, for use in @@ -1146,12 +1146,19 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr) * inheritance set. * * We have to handle this case differently from cases where a source relation - * is an inheritance set. Source inheritance is expanded at the bottom of the - * plan tree (see allpaths.c), but target inheritance has to be expanded at - * the top. The reason is that for UPDATE, each target relation needs a - * different targetlist matching its own column set. Fortunately, - * the UPDATE/DELETE target can never be the nullable side of an outer join, - * so it's OK to generate the plan this way. + * is an inheritance set. That's mainly because for UPDATE, each target + * relation needs a different targetlist matching its own column set. So, we + * must modify the source scan/join path for each target relation such that it + * produces the desired target list. + * + * The source scan/join paths for individual target relations are still + * created in allpaths.c by first expanding the inheritance in the usual way + * by set_append_rel_size, followed by join planning for each target relation + * separately in make_one_rel. Finally, we apply grouping_planner here to each + * child scan/join path so that it produces the desired targetlist. + * + * Fortunately, the UPDATE/DELETE target can never be the nullable side of an + * outer join, so it's OK to generate the plan this way. * * Returns nothing; the useful output is in the Paths we attach to * the (UPPERREL_FINAL, NULL) upperrel stored in *root. @@ -1164,14 +1171,8 @@ inheritance_planner(PlannerInfo *root) { Query *parse = root->parse; int top_parentRTindex = parse->resultRelation; - Bitmapset *subqueryRTindexes; - Bitmapset *modifiableARIindexes; int nominalRelation = -1; Index rootRelation = 0; - List *final_rtable = NIL; - int save_rel_array_size = 0; - RelOptInfo **save_rel_array = NULL; - AppendRelInfo **save_append_rel_array = NULL; List *subpaths = NIL; List *subroots = NIL; List *resultRelations = NIL; @@ -1179,70 +1180,60 @@ inheritance_planner(PlannerInfo *root) List *returningLists = NIL; List *rowMarks; RelOptInfo *final_rel; - ListCell *lc; - Index rti; + ListCell *lc1, + *lc2; RangeTblEntry *parent_rte; - PlannerInfo *parent_root; - Query *parent_parse; - Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); - PlannerInfo **parent_roots = NULL; + List *tlist, + *orig_tlist; + standard_qp_extra qp_extra; + RelOptInfo *planned_rel; + List *partitioned_rels = NIL; + /* Inheritance is never used for insert. */ Assert(parse->commandType != CMD_INSERT); + parent_rte = planner_rt_fetch(top_parentRTindex, root); /* - * We generate a modified instance of the original Query for each target - * relation, plan that, and put all the plans into a list that will be - * controlled by a single ModifyTable node. All the instances share the - * same rangetable, but each instance must have its own set of subquery - * RTEs within the finished rangetable because (1) they are likely to get - * scribbled on during planning, and (2) it's not inconceivable that - * subqueries could get planned differently in different cases. We need - * not create duplicate copies of other RTE kinds, in particular not the - * target relations, because they don't have either of those issues. Not - * having to duplicate the target relations is important because doing so - * (1) would result in a rangetable of length O(N^2) for N targets, with - * at least O(N^3) work expended here; and (2) would greatly complicate - * management of the rowMarks list. + * Generate the access paths for all relations mentioned in the query, + * including the target inheritance set. When doing the join planning, + * references in the join tree to the original target relation that's the + * root parent of the inheritance tree is replaced by each of its + * inheritance children and the resulting joinrel RelOptInfo's are + * added to root->inh_target_child_joinrels. * - * To begin with, generate a bitmapset of the relids of the subquery RTEs. + * Final planning steps (grouping_planner) are applied to the best path + * of each of those child joinrels using a modified instance of the + * original query for a given child target rel. All the paths so generated + * are put into a list that will be controlled by a single ModifyTable + * node. All the instances share the same rangetable. */ - subqueryRTindexes = NULL; - rti = 1; - foreach(lc, parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - if (rte->rtekind == RTE_SUBQUERY) - subqueryRTindexes = bms_add_member(subqueryRTindexes, rti); - rti++; - } /* - * Next, we want to identify which AppendRelInfo items contain references - * to any of the aforesaid subquery RTEs. These items will need to be - * copied and modified to adjust their subquery references; whereas the - * other ones need not be touched. It's worth being tense over this - * because we can usually avoid processing most of the AppendRelInfo - * items, thereby saving O(N^2) space and time when the target is a large - * inheritance tree. We can identify AppendRelInfo items by their - * child_relid, since that should be unique within the list. + * Save the unmodified version of the query's targetlist to be used below + * for passing to grouping_planner for each child target relation. */ - modifiableARIindexes = NULL; - if (subqueryRTindexes != NULL) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); + orig_tlist = list_copy(root->parse->targetList); - if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) || - bms_is_member(appinfo->child_relid, subqueryRTindexes) || - bms_overlap(pull_varnos((Node *) appinfo->translated_vars), - subqueryRTindexes)) - modifiableARIindexes = bms_add_member(modifiableARIindexes, - appinfo->child_relid); - } + /* Do the scan/join planning. */ + tlist = preprocess_targetlist(root); + qp_extra.tlist = tlist; + qp_extra.activeWindows = qp_extra.groupClause = NIL; + planned_rel = query_planner(root, tlist, standard_qp_callback, &qp_extra); + + /* + * If it turned out during query planning that all the children are dummy + * (pruned or excluded by constraints), no need to do the steps below. + * Let grouping_planner finish up the final path. + */ + if (IS_DUMMY_REL(planned_rel)) + { + grouping_planner(root, false, planned_rel, 0.0); + return; } + Assert(planned_rel->relid == top_parentRTindex); + Assert(planned_rel->reloptkind == RELOPT_BASEREL); + /* * If the parent RTE is a partitioned table, we should use that as the * nominal target relation, because the RTEs added for partitioned tables @@ -1250,67 +1241,50 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; rootRelation = top_parentRTindex; + partitioned_rels = lappend(partitioned_rels, + list_make1_int(top_parentRTindex)); } /* - * The PlannerInfo for each child is obtained by translating the relevant - * members of the PlannerInfo for its immediate parent, which we find - * using the parent_relid in its AppendRelInfo. We save the PlannerInfo - * for each parent in an array indexed by relid for fast retrieval. Since - * the maximum number of parents is limited by the number of RTEs in the - * query, we use that number to allocate the array. An extra entry is - * needed since relids start from 1. + * Get on with finalizing the path for each child target relation by + * calling grouping_planner on its joinrel. Note that we're restoring + * the query's targetlist to the original one for grouping_planner's + * targetlist expansion steps to perform the expansion with individual + * child descriptors. */ - parent_roots = (PlannerInfo **) palloc0((list_length(parse->rtable) + 1) * - sizeof(PlannerInfo *)); - parent_roots[top_parentRTindex] = root; - - /* - * And now we can get on with generating a plan for each child table. - */ - foreach(lc, root->append_rel_list) + parse->targetList = orig_tlist; + forboth(lc1, root->inh_target_child_rels, + lc2, root->inh_target_child_joinrels) { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); + AppendRelInfo *appinfo = root->append_rel_array[lfirst_int(lc1)]; + RelOptInfo *child_joinrel = lfirst(lc2); PlannerInfo *subroot; RangeTblEntry *child_rte; RelOptInfo *sub_final_rel; Path *subpath; - - /* append_rel_list contains all append rels; ignore others */ - if (!bms_is_member(appinfo->parent_relid, parent_relids)) - continue; - - /* - * expand_inherited_rtentry() always processes a parent before any of - * that parent's children, so the parent_root for this relation should - * already be available. - */ - parent_root = parent_roots[appinfo->parent_relid]; - Assert(parent_root != NULL); - parent_parse = parent_root->parse; + Relids child_relids = bms_make_singleton(appinfo->child_relid), + top_parent_relids = bms_make_singleton(top_parentRTindex); /* * We need a working copy of the PlannerInfo so that we can control * propagation of information back to the main copy. */ subroot = makeNode(PlannerInfo); - memcpy(subroot, parent_root, sizeof(PlannerInfo)); + memcpy(subroot, root, sizeof(PlannerInfo)); /* * Generate modified query with this rel as target. We first apply * adjust_appendrel_attrs, which copies the Query and changes - * references to the parent RTE to refer to the current child RTE, - * then fool around with subquery RTEs. + * references to the parent RTE to refer to the current child RTE. */ subroot->parse = (Query *) - adjust_appendrel_attrs(parent_root, - (Node *) parent_parse, - 1, &appinfo); + adjust_appendrel_attrs_multilevel(root, (Node *) parse, + child_relids, + top_parent_relids); /* * If there are securityQuals attached to the parent, move them to the @@ -1322,33 +1296,10 @@ inheritance_planner(PlannerInfo *root) parent_rte->securityQuals = NIL; /* - * Mark whether we're planning a query to a partitioned table or an - * inheritance parent. + * inheritance_make_rel_from_joinlist only adds the leaf partitions to + * PlannerInfo. */ - subroot->inhTargetKind = - (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED; - - /* - * If this child is further partitioned, remember it as a parent. - * Since a partitioned table does not have any data, we don't need to - * create a plan for it, and we can stop processing it here. We do, - * however, need to remember its modified PlannerInfo for use when - * processing its children, since we'll update their varnos based on - * the delta from immediate parent to child, not from top to child. - * - * Note: a very non-obvious point is that we have not yet added - * duplicate subquery RTEs to the subroot's rtable. We mustn't, - * because then its children would have two sets of duplicates, - * confusing matters. - */ - if (child_rte->inh) - { - Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE); - parent_relids = bms_add_member(parent_relids, appinfo->child_relid); - parent_roots[appinfo->child_relid] = subroot; - - continue; - } + Assert(!child_rte->inh); /* * Set the nominal target relation of the ModifyTable node if not @@ -1374,111 +1325,8 @@ inheritance_planner(PlannerInfo *root) if (nominalRelation < 0) nominalRelation = appinfo->child_relid; - /* - * The rowMarks list might contain references to subquery RTEs, so - * make a copy that we can apply ChangeVarNodes to. (Fortunately, the - * executor doesn't need to see the modified copies --- we can just - * pass it the original rowMarks list.) - */ - subroot->rowMarks = copyObject(parent_root->rowMarks); - - /* - * The append_rel_list likewise might contain references to subquery - * RTEs (if any subqueries were flattenable UNION ALLs). So prepare - * to apply ChangeVarNodes to that, too. As explained above, we only - * want to copy items that actually contain such references; the rest - * can just get linked into the subroot's append_rel_list. - * - * If we know there are no such references, we can just use the outer - * append_rel_list unmodified. - */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - subroot->append_rel_list = NIL; - foreach(lc2, parent_root->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, modifiableARIindexes)) - appinfo2 = copyObject(appinfo2); - - subroot->append_rel_list = lappend(subroot->append_rel_list, - appinfo2); - } - } - - /* - * Add placeholders to the child Query's rangetable list to fill the - * RT indexes already reserved for subqueries in previous children. - * These won't be referenced, so there's no need to make them very - * valid-looking. - */ - while (list_length(subroot->parse->rtable) < list_length(final_rtable)) - subroot->parse->rtable = lappend(subroot->parse->rtable, - makeNode(RangeTblEntry)); - - /* - * If this isn't the first child Query, generate duplicates of all - * subquery RTEs, and adjust Var numbering to reference the - * duplicates. To simplify the loop logic, we scan the original rtable - * not the copy just made by adjust_appendrel_attrs; that should be OK - * since subquery RTEs couldn't contain any references to the target - * rel. - */ - if (final_rtable != NIL && subqueryRTindexes != NULL) - { - ListCell *lr; - - rti = 1; - foreach(lr, parent_parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lr); - - if (bms_is_member(rti, subqueryRTindexes)) - { - Index newrti; - - /* - * The RTE can't contain any references to its own RT - * index, except in its securityQuals, so we can save a - * few cycles by applying ChangeVarNodes to the rest of - * the rangetable before we append the RTE to it. - */ - newrti = list_length(subroot->parse->rtable) + 1; - ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); - ChangeVarNodes((Node *) subroot->rowMarks, rti, newrti, 0); - /* Skip processing unchanging parts of append_rel_list */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - foreach(lc2, subroot->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, - modifiableARIindexes)) - ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); - } - } - rte = copyObject(rte); - ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); - subroot->parse->rtable = lappend(subroot->parse->rtable, - rte); - } - rti++; - } - } - - /* There shouldn't be any OJ info to translate, as yet */ - Assert(subroot->join_info_list == NIL); - /* and we haven't created PlaceHolderInfos, either */ - Assert(subroot->placeholder_list == NIL); - - /* Generate Path(s) for accessing this result relation */ - grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ ); + /* Apply the top-level targetlist to child_joinrel's path. */ + grouping_planner(subroot, true, child_joinrel, 0.0); /* * Select cheapest path in case there's more than one. We always run @@ -1490,45 +1338,10 @@ inheritance_planner(PlannerInfo *root) subpath = sub_final_rel->cheapest_total_path; /* - * If this child rel was excluded by constraint exclusion, exclude it - * from the result plan. + * child rel cannot be empty, or inheritance_make_rel_from_joinlist + * wouldn't have put it in the list to begin with. */ - if (IS_DUMMY_PATH(subpath)) - continue; - - /* - * If this is the first non-excluded child, its post-planning rtable - * becomes the initial contents of final_rtable; otherwise, append - * just its modified subquery RTEs to final_rtable. - */ - if (final_rtable == NIL) - final_rtable = subroot->parse->rtable; - else - final_rtable = list_concat(final_rtable, - list_copy_tail(subroot->parse->rtable, - list_length(final_rtable))); - - /* - * We need to collect all the RelOptInfos from all child plans into - * the main PlannerInfo, since setrefs.c will need them. We use the - * last child's simple_rel_array (previous ones are too short), so we - * have to propagate forward the RelOptInfos that were already built - * in previous children. - */ - Assert(subroot->simple_rel_array_size >= save_rel_array_size); - for (rti = 1; rti < save_rel_array_size; rti++) - { - RelOptInfo *brel = save_rel_array[rti]; - - if (brel) - subroot->simple_rel_array[rti] = brel; - } - save_rel_array_size = subroot->simple_rel_array_size; - save_rel_array = subroot->simple_rel_array; - save_append_rel_array = subroot->append_rel_array; - - /* Make sure any initplans from this rel get into the outer list */ - root->init_plans = subroot->init_plans; + Assert(!IS_DUMMY_PATH(subpath)); /* Build list of sub-paths */ subpaths = lappend(subpaths, subpath); @@ -1560,36 +1373,6 @@ inheritance_planner(PlannerInfo *root) */ /* - * If we managed to exclude every child rel, return a dummy plan; it - * doesn't even need a ModifyTable node. - */ - if (subpaths == NIL) - { - set_dummy_rel_pathlist(final_rel); - return; - } - - /* - * Put back the final adjusted rtable into the master copy of the Query. - * (We mustn't do this if we found no non-excluded children.) - */ - parse->rtable = final_rtable; - root->simple_rel_array_size = save_rel_array_size; - root->simple_rel_array = save_rel_array; - root->append_rel_array = save_append_rel_array; - - /* Must reconstruct master's simple_rte_array, too */ - root->simple_rte_array = (RangeTblEntry **) - palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *)); - rti = 1; - foreach(lc, final_rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - root->simple_rte_array[rti++] = rte; - } - - /* * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will * have dealt with fetching non-locked marked rows, else we need to have * ModifyTable do that. @@ -1629,6 +1412,12 @@ inheritance_planner(PlannerInfo *root) * (inheritance_planner will create a single ModifyTable node covering all the * target tables.) * + * If non-NULL, planned_rel is a RelOptInfo containing paths for the query's + * top-level joinrel, which the caller produced by itself. In that case, this + * function only needs to adjust the targetlist of its cheapest_total_path. + * The only caller that may pass such a RelOptInfo currently is + * inheritance_planner. + * * tuple_fraction is the fraction of tuples we expect will be retrieved. * tuple_fraction is interpreted as follows: * 0: expect all tuples to be retrieved (normal case) @@ -1647,6 +1436,7 @@ inheritance_planner(PlannerInfo *root) */ static void grouping_planner(PlannerInfo *root, bool inheritance_update, + RelOptInfo *planned_rel, double tuple_fraction) { Query *parse = root->parse; @@ -1659,7 +1449,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, List *final_targets; List *final_targets_contain_srfs; bool final_target_parallel_safe; - RelOptInfo *current_rel; + RelOptInfo *current_rel = planned_rel; RelOptInfo *final_rel; ListCell *lc; @@ -1699,6 +1489,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * special work for recursive unions is the responsibility of * plan_set_operations. */ + Assert(current_rel == NULL); current_rel = plan_set_operations(root); /* @@ -1878,8 +1669,9 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * We also generate (in standard_qp_callback) pathkey representations * of the query's sort clause, distinct clause, etc. */ - current_rel = query_planner(root, tlist, - standard_qp_callback, &qp_extra); + if (current_rel == NULL) + current_rel = query_planner(root, tlist, + standard_qp_callback, &qp_extra); /* * Convert the query's result tlist into PathTarget format. diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index cd6e11904e..78baec00dc 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -917,7 +917,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->grouping_map = NULL; subroot->minmax_aggs = NIL; subroot->qual_security_level = 0; - subroot->inhTargetKind = INHKIND_NONE; subroot->hasRecursion = false; subroot->wt_param_id = -1; subroot->non_recursive_path = NULL; diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index d5720518a8..ef1f978889 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -2253,8 +2253,39 @@ adjust_appendrel_attrs_mutator(Node *node, context->appinfos); return (Node *) phv; } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 0c88c90de4..ed0953f9e1 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1264,36 +1264,6 @@ get_relation_constraints(PlannerInfo *root, } } - /* - * Append partition predicates, if any. - * - * For selects, partition pruning uses the parent table's partition bound - * descriptor, instead of constraint exclusion which is driven by the - * individual partition's partition constraint. - */ - if (enable_partition_pruning && root->parse->commandType != CMD_SELECT) - { - List *pcqual = RelationGetPartitionQual(relation); - - if (pcqual) - { - /* - * Run the partition quals through const-simplification similar to - * check constraints. We skip canonicalize_qual, though, because - * partition quals should be in canonical form already; also, - * since the qual is in implicit-AND format, we'd have to - * explicitly convert it to explicit-AND format and back again. - */ - pcqual = (List *) eval_const_expressions(root, (Node *) pcqual); - - /* Fix Vars to have the desired varno */ - if (varno != 1) - ChangeVarNodes((Node *) pcqual, 1, varno, 0); - - result = list_concat(result, pcqual); - } - } - heap_close(relation, NoLock); return result; @@ -1420,31 +1390,15 @@ relation_excluded_by_constraints(PlannerInfo *root, switch (constraint_exclusion) { case CONSTRAINT_EXCLUSION_OFF: - - /* - * Don't prune if feature turned off -- except if the relation is - * a partition. While partprune.c-style partition pruning is not - * yet in use for all cases (update/delete is not handled), it - * would be a UI horror to use different user-visible controls - * depending on such a volatile implementation detail. Therefore, - * for partitioned tables we use enable_partition_pruning to - * control this behavior. - */ - if (root->inhTargetKind == INHKIND_PARTITIONED) - break; return false; case CONSTRAINT_EXCLUSION_PARTITION: /* * When constraint_exclusion is set to 'partition' we only handle - * OTHER_MEMBER_RELs, or BASERELs in cases where the result target - * is an inheritance parent or a partitioned table. + * OTHER_MEMBER_RELs. */ - if ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) && - !(rel->reloptkind == RELOPT_BASEREL && - root->inhTargetKind != INHKIND_NONE && - rel->relid == root->parse->resultRelation)) + if (rel->reloptkind != RELOPT_OTHER_MEMBER_REL) return false; break; diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 32ac3315a4..29339d6c72 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -319,9 +319,6 @@ typedef struct PlannerInfo Index qual_security_level; /* minimum security_level for quals */ /* Note: qual_security_level is zero if there are no securityQuals */ - InheritanceKind inhTargetKind; /* indicates if the target relation is an - * inheritance child or partition or a - * partitioned table */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */ bool hasDeletedRTEs; /* true if any RTE was deleted from jointree */ @@ -343,6 +340,21 @@ typedef struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* + * The following fields are set during query planning portion of an + * inherited UPDATE/DEELETE operation. + */ + + /* RT indexes of child target rels */ + List *inh_target_child_rels; + + /* + * RelOptInfos representing the output of make_rel_from_joinlist() for + * a each child rel in the above list, acting as the target relation in + * place of the original parent target relation. + */ + List *inh_target_child_joinrels; } PlannerInfo; diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index 3ba3aaf2d8..a539280851 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1781,7 +1781,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_1 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_1 -> Nested Loop Semi Join @@ -1789,7 +1789,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_2 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_2 (28 rows) -- 2.11.0
v5-0003-Lazy-creation-of-RTEs-for-inheritance-children.patch (text/plain, 85.1K) download From 42c9c879f1793ea1fb5a6328e853139a40cfcc8d Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 18:14:13 +0900 Subject: [PATCH v5 3/6] Lazy creation of RTEs for inheritance children Currently, expand_inherited_tables called from subquery_planner opens all inheritance child tables and adds them to the query's range table and PlannerInfo in the form of AppendRelInfo's initially and later RelOptInfo's. For partitioning, that's pretty wasteful, because it's possible to determine only the partitions that will need to be scanned using partition pruning, which doesn't require opening the partitions themselves. However, the point at which the above processing happens is too early to perform partition pruning. This commit rearranges things within the planner so that inheritance child relations can be added to the Query and PlannerInfo when peforming set_append_rel_size on the parent table. Although the late initialization approach only benefits the partitioning case, it seems undesirable to do it only for partitioned tables, because it means adding code to handle partitions specially at various places within the planner. So, *all* inheritance parent tables are expanded at set_append_rel_size. All unpruned partitions are added to Query in the form of their RangeTblEntry's added to the range table and to PlannerInfo in the form of an AppendRelInfo and a RelOptInfo for each. For partitioning, although we don't create a RangeTblEntry and RelOptInfo for pruned partitions at set_append_rel_size time, partitionwise join code relies on the fact that even though partitions may have been pruned, they'd still own a RelOptInfo to handle the outer join case where the pruned partition appears on the nullable side of join. Partitionwise join code deals with that by allocating dummy RelOptInfos for pruned partitions that are based mostly on their parent's properties. There are some regression test diffs: 1. Caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child and also that the individual partition RT entries are now created in the order in which their parent's are processed, whereas previously they'd be added to the range table in the order of depth-first expansion of inheritance tree. 2. Those in postgres_fdw.out are caused by the fact that junk columns required for row marking are added to reltarget->exprs later than user columns, because the row marking junk columns arent added until after the inheritance is expanded which as of this commit is later than it used to be. --- contrib/postgres_fdw/expected/postgres_fdw.out | 32 +- src/backend/optimizer/path/allpaths.c | 204 +----- src/backend/optimizer/path/joinrels.c | 62 ++ src/backend/optimizer/plan/initsplan.c | 58 -- src/backend/optimizer/plan/planner.c | 54 +- src/backend/optimizer/plan/setrefs.c | 6 + src/backend/optimizer/prep/preptlist.c | 11 +- src/backend/optimizer/prep/prepunion.c | 818 +++++++++++++++------- src/backend/optimizer/util/Makefile | 2 +- src/backend/optimizer/util/plancat.c | 41 +- src/backend/optimizer/util/relnode.c | 80 +-- src/backend/partitioning/partprune.c | 43 +- src/include/nodes/relation.h | 8 + src/include/optimizer/pathnode.h | 1 + src/include/optimizer/plancat.h | 2 +- src/include/optimizer/prep.h | 15 +- src/include/partitioning/partprune.h | 2 +- src/test/regress/expected/join.out | 22 +- src/test/regress/expected/partition_aggregate.out | 4 +- 19 files changed, 834 insertions(+), 631 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 21a2ef5ad3..c930bf7bab 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7085,15 +7085,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7123,15 +7123,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7160,15 +7160,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.tableoid, foo.* -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.tableoid, foo.* Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.tableoid, foo.* -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.tableoid, foo2.* Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid @@ -7178,15 +7178,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.tableoid, foo.* -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.tableoid, foo.* Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.tableoid, foo.* -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.tableoid, foo2.* Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (39 rows) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 8de8d383f9..6ee800d84a 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -44,7 +44,6 @@ #include "optimizer/var.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -943,8 +942,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -952,29 +949,22 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Assert(IS_SIMPLE_REL(rel)); /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, we will bubble up - * the indexes of partitioned relations that appear down in the tree, so - * that when we've created Paths for all the children, the root - * partitioned table's list will contain all such indexes. + * Add append children to the PlannerInfo. */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - rel->partitioned_child_rels = list_make1_int(rti); + expand_append_rel(root, rel, rte, rti); + + /* Did we find out that none of the children produce any output? */ + if (IS_DUMMY_REL(rel)) + return; /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. + * If we only need to scan the parent because all of the children are + * either pruned or excluded, do so right away and return. */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) + if (!rte->inh) { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; + set_rel_size(root, rel, rti, rte); + return; } /* @@ -1014,12 +1004,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; ListCell *parentvars; ListCell *childvars; - ListCell *lc; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1027,18 +1013,31 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); - /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. - */ + /* The child rel's RelOptInfo is created during expand_append_rel */ childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); /* - * Copy/Modify targetlist. Even if this child is deemed empty, we need - * its targetlist in case it falls on nullable side in a child-join - * because of partitionwise join. + * Child relation may have be marked dummy if build_append_child_rel + * found self-contradictory quals. + */ + if (IS_DUMMY_REL(childrel)) + continue; + + if (relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* + * This child need not be scanned, so we can omit it from the + * appendrel. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Copy/Modify targetlist. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -1067,144 +1066,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, add_child_rel_equivalences(root, appinfo, rel, childrel); childrel->has_eclass_joins = rel->has_eclass_joins; - /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* - * This child need not be scanned, so we can omit it from the - * appendrel. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - /* CE failed, so finish copying/modifying join quals. */ childrel->joininfo = (List *) adjust_appendrel_attrs(root, @@ -1275,7 +1136,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, child_relids, top_parent_relids); root->parse = parse; - tlist = preprocess_targetlist(root); + tlist = preprocess_targetlist(root, true); build_base_rel_tlists(root, tlist); root->parse = orig_parse; @@ -2744,6 +2605,7 @@ inheritance_make_rel_from_joinlist(PlannerInfo *root, continue; childrel = find_base_rel(root, appinfo->child_relid); + Assert(childrel != NULL); /* Ignore excluded/pruned children. */ if (IS_DUMMY_REL(childrel)) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index d3d21fed5d..da0831de4e 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -20,6 +20,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/prep.h" +#include "optimizer/tlist.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -46,6 +47,9 @@ static void try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, List *parent_restrictlist); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + int partidx); /* @@ -1376,6 +1380,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts); + if (child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts); + /* We should never try to join two overlapping sets of rels. */ Assert(!bms_overlap(child_rel1->relids, child_rel2->relids)); child_joinrelids = bms_union(child_rel1->relids, child_rel2->relids); @@ -1582,3 +1591,56 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx) +{ + RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + /* Pass parent's info as for both the parent rel and child rel. */ + AppendRelInfo *appinfo = make_append_rel_info(parent, parentrte, + parent->tupdesc, + parentrte->relid, + parent->reltype, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 01335db511..7636aa82c4 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -616,64 +616,6 @@ create_lateral_join_info(PlannerInfo *root) bms_add_member(brel2->lateral_referencers, rti); } } - - /* - * Lastly, propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - */ - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - RangeTblEntry *brte = root->simple_rte_array[rti]; - - /* - * Skip empty slots. Also skip non-simple relations i.e. dead - * relations. - */ - if (brel == NULL || !IS_SIMPLE_REL(brel)) - continue; - - /* - * In the case of table inheritance, the parent RTE is directly linked - * to every child table via an AppendRelInfo. In the case of table - * partitioning, the inheritance hierarchy is expanded one level at a - * time rather than flattened. Therefore, an other member rel that is - * a partitioned table may have children of its own, and must - * therefore be marked with the appropriate lateral info so that those - * children eventually get marked also. - */ - Assert(brte); - if (brel->reloptkind == RELOPT_OTHER_MEMBER_REL && - (brte->rtekind != RTE_RELATION || - brte->relkind != RELKIND_PARTITIONED_TABLE)) - continue; - - if (brte->inh) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); - RelOptInfo *childrel; - - if (appinfo->parent_relid != rti) - continue; - childrel = root->simple_rel_array[appinfo->child_relid]; - Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - Assert(childrel->direct_lateral_relids == NULL); - childrel->direct_lateral_relids = brel->direct_lateral_relids; - Assert(childrel->lateral_relids == NULL); - childrel->lateral_relids = brel->lateral_relids; - Assert(childrel->lateral_referencers == NULL); - childrel->lateral_referencers = brel->lateral_referencers; - } - } - } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 93b8c761c2..84746a9bc9 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -23,6 +23,7 @@ #include "access/sysattr.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -699,27 +700,24 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } /* * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). + * pullup (so that all non-inherited RTEs are present). */ preprocess_rowmarks(root); /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain base relations not joins, so it's OK (and marginally more - * efficient) to do it after checking for join RTEs. We must do it after - * pulling up subqueries, else we'd fail to handle inherited tables in - * subqueries. - */ - expand_inherited_tables(root); - - /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -1215,7 +1213,7 @@ inheritance_planner(PlannerInfo *root) orig_tlist = list_copy(root->parse->targetList); /* Do the scan/join planning. */ - tlist = preprocess_targetlist(root); + tlist = preprocess_targetlist(root, false); qp_extra.tlist = tlist; qp_extra.activeWindows = qp_extra.groupClause = NIL; planned_rel = query_planner(root, tlist, standard_qp_callback, &qp_extra); @@ -1223,9 +1221,10 @@ inheritance_planner(PlannerInfo *root) /* * If it turned out during query planning that all the children are dummy * (pruned or excluded by constraints), no need to do the steps below. - * Let grouping_planner finish up the final path. + * For regular inheritance case, it's possible that we only need to modify + * the parent table. Let grouping_planner finish up the final path. */ - if (IS_DUMMY_REL(planned_rel)) + if (!parent_rte->inh || IS_DUMMY_REL(planned_rel)) { grouping_planner(root, false, planned_rel, 0.0); return; @@ -1580,7 +1579,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, } /* Preprocess targetlist */ - tlist = preprocess_targetlist(root); + tlist = preprocess_targetlist(root, inheritance_update); /* * We are now done hacking up the query's targetlist. Most of the @@ -1674,6 +1673,15 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, standard_qp_callback, &qp_extra); /* + * Query planning may have added some columns to the top-level tlist, + * which happens when there are row marks applied to inheritance + * parent relations (additional junk columns needed for applying row + * marks are added after expanding inheritance.) + */ + if (list_length(tlist) < list_length(root->processed_tlist)) + tlist = root->processed_tlist; + + /* * Convert the query's result tlist into PathTarget format. * * Note: it's desirable to not do this till after query_planner(), @@ -2360,7 +2368,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2385,7 +2393,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } @@ -6765,6 +6773,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -6866,6 +6878,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 6d6ef1c376..454870609a 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -326,6 +326,12 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) UPPERREL_FINAL, NULL))) add_rtes_to_flat_rtable(rel->subroot, true); } + /* + * A NULL rel also means an unplanned subquery rte, so apply + * flatten_unplanned_rtes. + */ + else + flatten_unplanned_rtes(glob, rte); } rti++; } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 8603feef2b..9c0a1c1b1c 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -67,7 +67,7 @@ static List *expand_targetlist(List *tlist, int command_type, * is also preprocessed (and updated in-place). */ List * -preprocess_targetlist(PlannerInfo *root) +preprocess_targetlist(PlannerInfo *root, bool inheritance_update) { Query *parse = root->parse; int result_relation = parse->resultRelation; @@ -134,6 +134,15 @@ preprocess_targetlist(PlannerInfo *root) if (rc->rti != rc->prti) continue; + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which affects which junk columns + * are needed in the top-level tlist. + */ + if (rc->isParent && !inheritance_update) + continue; + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) { /* Need to fetch TID */ diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index ef1f978889..c6817757e7 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -39,15 +39,19 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" +#include "optimizer/var.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" +#include "partitioning/partprune.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/selfuncs.h" @@ -100,22 +104,22 @@ static List *generate_append_tlist(List *colTypes, List *colCollations, List *refnames_tlist); static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist); static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); + Index rti, RelOptInfo *rel); static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); -static void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel); +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); +static RelOptInfo *build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, @@ -1463,42 +1467,107 @@ generate_setop_grouplist(SetOperationStmt *op, List *targetlist) /* - * expand_inherited_tables - * Expand each rangetable entry that represents an inheritance set - * into an "append relation". At the conclusion of this process, - * the "inh" flag is set in all and only those RTEs that are append - * relation parents. + * expand_append_rel + * This initializes RelOptInfos for an appendrel's child relations. + * + * 'rel' is the appendrel parent, whose range table entry ('rte') has been + * marked to require adding children. An appendrel parent could either + * be a subquery (if we flattened UNION ALL query) or a table that's known + * to have inheritance children. The latter consists of both regular + * inheritance parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as an appendrel child. The original RTE is + * considered to represent the whole inheritance set. */ void -expand_inherited_tables(PlannerInfo *root) +expand_append_rel(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, + Index rti) { - Index nrtes; - Index rti; - ListCell *rl; + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); /* - * expand_inherited_rtentry may add RTEs to parse->rtable. The function is - * expected to recursively handle any RTEs that it creates with inh=true. - * So just scan as far as the original end of the rtable list. + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) + if (rte->rtekind == RTE_SUBQUERY) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); + ListCell *l; - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_append_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_inherited_rtentry(root, rte, rti, rel); } } /* * expand_inherited_rtentry - * Check whether a rangetable entry represents an inheritance set. - * If so, add entries for all the child tables to the query's - * rangetable, and build AppendRelInfo nodes for all the child tables - * and add them to root->append_rel_list. If not, clear the entry's - * "inh" flag to prevent later code from looking for AppendRelInfos. + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. * * Note that the original RTE is considered to represent the whole * inheritance set. The first of the generated RTEs is an RTE for the same @@ -1509,45 +1578,22 @@ expand_inherited_tables(PlannerInfo *root) * regular inheritance, a parent RTE must always have at least two associated * AppendRelInfos: one corresponding to the parent table as a simple member of * inheritance set and one or more corresponding to the actual children. - * Since a partitioned table is not scanned, it might have only one associated - * AppendRelInfo. */ static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, + RelOptInfo *rel) { Oid parentOID; PlanRowMark *oldrc; - Relation oldrelation; - LOCKMODE lockmode; + LOCKMODE lockmode = rte->rellockmode; List *inhOIDs; ListCell *l; + int num_children; + int num_children_added = 0; - /* Does RT entry allow inheritance? */ - if (!rte->inh) - return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } - /* Fast path for common case of childless table */ + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); parentOID = rte->relid; - if (!has_subclass(parentOID)) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * The rewriter should already have obtained an appropriate lock on each - * relation named in the query. However, for each child relation we add - * to the query, we must obtain an appropriate lock, because this will be - * the first use of those relations in the parse/rewrite/plan pipeline. - * Child rels should use the same lockmode as their parent. - */ - lockmode = rte->rellockmode; /* Scan for all members of inheritance set, acquire needed locks */ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); @@ -1557,7 +1603,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) * case. This could happen despite above has_subclass() check, if table * once had a child but no longer does. */ - if (list_length(inhOIDs) < 2) + num_children = list_length(inhOIDs); + if (num_children < 2) { /* Clear flag before returning */ rte->inh = false; @@ -1565,277 +1612,246 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } /* - * If parent relation is selected FOR UPDATE/SHARE, we need to mark its - * PlanRowMark as isParent = true, and generate a new PlanRowMark for each - * child. + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. */ oldrc = get_plan_rowmark(root->rowMarks, rti); - if (oldrc) - oldrc->isParent = true; + Assert(oldrc == NULL || oldrc->isParent); /* - * Must open the parent relation to examine its tupdesc. We need not lock - * it; we assume the rewriter already did. + * Must expand PlannerInfo arrays by num_children before we can add + * children. */ - oldrelation = heap_open(parentOID, NoLock); + expand_planner_arrays(root, num_children); - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) + foreach(l, inhOIDs) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); + Oid childOID = lfirst_oid(l); + Relation newrelation; + RelOptInfo *childrel; + + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); - } - else - { - List *appinfos = NIL; - RangeTblEntry *childrte; - Index childRTindex; - - /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. - */ - foreach(l, inhOIDs) + if (RELATION_IS_OTHER_TEMP(newrelation)) { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = heap_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - heap_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - heap_close(newrelation, NoLock); + heap_close(newrelation, lockmode); + continue; } - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); + childrel = add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation); + Assert(childrel != NULL); + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; } - heap_close(oldrelation, NoLock); + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } } /* * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo * - * Note that RelationGetPartitionDispatchInfo will expand partitions in the - * same order as this code. + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * in order to avoid partitions being locked in a different order than other + * places in the backend that may lock partitions. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) + Index parentRTindex, RelOptInfo *parentrel) { + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; - check_stack_depth(); - - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + expand_planner_arrays(root, bms_num_members(partindexes)); /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. */ - if (partdesc->nparts == 0) - { - parentrte->inh = false; - return; - } + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); - for (i = 0; i < partdesc->nparts; i++) + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) { - Oid childOID = partdesc->oids[i]; - Relation childrel; + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; - /* Open rel; we already have required locks */ - childrel = heap_open(childOID, NoLock); + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + } - /* Close child relation, but keep locks */ - heap_close(childrel, NoLock); + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); } } /* - * expand_single_inheritance_child - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) + * The return value is the RelOptInfo that's added. * * PlanRowMarks still carry the top-parent's RTI, and the top-parent's * allMarkTypes field still accumulates values from all descendents. * * "parentrte" and "parentRTindex" are immediate parent's RTE and * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. */ -static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); Oid childOID = RelationGetRelid(childrel); RangeTblEntry *childrte; Index childRTindex; AppendRelInfo *appinfo; + RelOptInfo *childrelopt; /* * Build an RTE for the child, and attach to query's rangetable list. We * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh = false. Also, set requiredPerms to zero since - * all required permissions checks are done on the original RTE. Likewise, - * set the child's securityQuals to empty, because we only want to apply - * the parent's RLS conditions regardless of what RLS properties + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties * individual children may have. (This is an intentional choice to make * inherited RLS work like regular permissions checks.) The parent * securityQuals will be propagated to children along with other base * restriction clauses, so we don't need to do it here. */ childrte = copyObject(parentrte); - *childrte_p = childrte; childrte->relid = childOID; childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) - childrte->inh = true; - else - childrte->inh = false; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); childrte->requiredPerms = 0; childrte->securityQuals = NIL; parse->rtable = lappend(parse->rtable, childrte); childRTindex = list_length(parse->rtable); - *childRTindex_p = childRTindex; + + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + childrel->rd_att, + childOID, + childrel->rd_rel->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) + if (childrte->relid != parentrte->relid) { - appinfo = makeNode(AppendRelInfo); - appinfo->parent_relid = parentRTindex; - appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->rd_rel->reltype; - appinfo->child_reltype = childrel->rd_rel->reltype; - make_inh_translation_list(parentrel, childrel, childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = parentOID; - *appinfos = lappend(*appinfos, appinfo); - - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); } /* @@ -1867,6 +1883,24 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need need to create + * one, because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_append_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; } /* @@ -1877,14 +1911,11 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, * For paranoia's sake, we match type/collation as well as attribute name. */ static void -make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, - List **translated_vars) +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) { List *vars = NIL; - TupleDesc old_tupdesc = RelationGetDescr(oldrelation); - TupleDesc new_tupdesc = RelationGetDescr(newrelation); - Oid new_relid = RelationGetRelid(newrelation); int oldnatts = old_tupdesc->natts; int newnatts = new_tupdesc->natts; int old_attno; @@ -1914,7 +1945,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, * When we are generating the "translation list" for the parent table * of an inheritance set, no need to search for matches. */ - if (oldrelation == newrelation) + if (from_rel == to_rel) { vars = lappend(vars, makeVar(newvarno, (AttrNumber) (old_attno + 1), @@ -1940,10 +1971,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, { HeapTuple newtup; - newtup = SearchSysCacheAttName(new_relid, attname); + newtup = SearchSysCacheAttName(to_rel, attname); if (!newtup) elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; ReleaseSysCache(newtup); @@ -1953,10 +1984,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, /* Found it, check type and collation match */ if (atttypid != att->atttypid || atttypmod != att->atttypmod) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); if (attcollation != att->attcollation) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); vars = lappend(vars, makeVar(newvarno, (AttrNumber) (new_attno + 1), @@ -2023,6 +2054,255 @@ translate_col_privs(const Bitmapset *parent_privs, } /* + * build_append_child_rel + * Build a RelOptInfo for child relation of an append rel + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields base on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turns out to be self-contradictory, the + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + List *childquals; + ListCell *lc; + bool have_const_false_cq; + Index cq_min_security; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = false; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, parent->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual = (Node *) rinfo->clause; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, childqual, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might + * have securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals.) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except + * that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* + * not likely that we'd see constants here, so no + * check + */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, + false, + false, + security_level, + NULL, NULL, + NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* Set child's version of baserestrictinfo. */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; +} + +AppendRelInfo * +make_append_rel_info(RelOptInfo *parentrel, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex) +{ + AppendRelInfo *appinfo = makeNode(AppendRelInfo); + + appinfo->parent_relid = parentrel->relid; + appinfo->child_relid = childRTindex; + appinfo->parent_reltype = parentrel->reltype; + appinfo->child_reltype = childreltype; + make_inh_translation_list(parentrel->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, + &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; + + return appinfo; +} + +/* * adjust_appendrel_attrs * Copy the specified query or expression and translate Vars referring to a * parent rel to refer to the corresponding child rel instead. We also diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile index c54d0a690d..da9ccf32b4 100644 --- a/src/backend/optimizer/util/Makefile +++ b/src/backend/optimizer/util/Makefile @@ -13,6 +13,6 @@ top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global OBJS = clauses.o joininfo.o orclauses.o pathnode.o placeholder.o \ - plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o + plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index ed0953f9e1..ce8a8fb78e 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -106,7 +106,7 @@ static void set_baserel_partition_key_exprs(Relation relation, */ void get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) + Bitmapset *updatedCols, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; @@ -444,11 +444,32 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, get_relation_foreign_keys(root, rel, relation, inhparent); /* - * Collect info about relation's partitioning scheme, if any. Only - * inheritance parents may be partitioned. + * Collect some additional information for inheritance parents. */ - if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - set_relation_partition_info(root, rel, relation); + if (inhparent) + { + /* + * We'll need the TupleDesc when initializing the child relation. + * A copy is being made because concurrent changes might drop + * the relcache entry. That's possible because ALTER TABLE + * child_table NO INHERIT parent_table only requires an + * AccessShareLock on parent_table. + */ + rel->tupdesc = CreateTupleDescCopy(RelationGetDescr(relation)); + rel->reltype = RelationGetForm(relation)->reltype; + + /* + * If partitioned, also save the information of partitioning scheme, + * and whether the query updates any of the partition key columns. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + set_relation_partition_info(root, rel, relation); + if (!root->partColsUpdated) + root->partColsUpdated = + has_partition_attrs(relation, updatedCols, NULL); + } + } heap_close(relation, NoLock); @@ -1854,16 +1875,20 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; + + /* + * Since we must've taken a lock on the table, it's okay to simply copy + * the pointers to relcache data here. + */ + rel->part_oids = partdesc->oids; + rel->boundinfo = partdesc->boundinfo; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 29ba19349f..c23db9d78d 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -130,6 +130,37 @@ setup_append_rel_array(PlannerInfo *root) } } +/* Expand the PlannerInfo arrays by add_size members and zero-init it. */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size = root->simple_rel_array_size + add_size; + + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + if (root->append_rel_array) + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + else + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + + /* Set the contents of just allocated memory to 0. */ + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + root->simple_rel_array_size = new_size; +} + /* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. @@ -238,7 +269,8 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte->relid, rte->inh, rte->updatedCols, + rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -279,52 +311,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); - /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. - */ - if (rte->inh) - { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); - - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; - } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); - } - return rel; } diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index d6ca03de4a..bf17ae22ba 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -45,6 +45,7 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/planner.h" #include "optimizer/predtest.h" @@ -436,17 +437,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -540,23 +547,20 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ @@ -564,6 +568,13 @@ prune_append_rel_partitions(RelOptInfo *rel) return NULL; /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. */ @@ -590,15 +601,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 29339d6c72..6b614e4794 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -15,6 +15,7 @@ #define RELATION_H #include "access/sdir.h" +#include "access/tupdesc.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/params.h" @@ -710,6 +711,7 @@ typedef struct RelOptInfo int nparts; /* number of partitions */ struct PartitionBoundInfoData *boundinfo; /* Partition bounds */ List *partition_qual; /* partition constraint */ + Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ List **partexprs; /* Non-nullable partition key expressions. */ @@ -719,6 +721,12 @@ typedef struct RelOptInfo Index inh_root_parent; /* For otherrels, this is the RT index of * inheritance table mentioned in the query * from which this relation originated */ + /* + * Set only if this is an inheritance parent relation. This information + * is needed when initializing the planning info for children. + */ + TupleDesc tupdesc; /* A "copy" of the table's tuple desriptor */ + Oid reltype; /* Table's reltype */ } RelOptInfo; /* diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 81abcf53a8..b1baa3117a 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -261,6 +261,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index 7d53cbbb87..edaf2a3b4f 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -26,7 +26,7 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); + bool inhparent, Bitmapset *updatedCols, RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 38608770a2..e8e21e2ed5 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -38,7 +38,7 @@ extern Expr *canonicalize_qual(Expr *qual, bool is_check); /* * prototypes for preptlist.c */ -extern List *preprocess_targetlist(PlannerInfo *root); +extern List *preprocess_targetlist(PlannerInfo *root, bool inheritance_update); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); @@ -46,12 +46,16 @@ extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); * prototypes for prepunion.c */ extern RelOptInfo *plan_set_operations(PlannerInfo *root); - -extern void expand_inherited_tables(PlannerInfo *root); - +extern void expand_append_rel(PlannerInfo *root, + RelOptInfo *rel, + RangeTblEntry *rte, + Index rti); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); - +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parentrel, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex); extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, Relids child_relids, Relids top_parent_relids); @@ -64,5 +68,4 @@ extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, Relids left_relids, Relids right_relids); extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, Relids child_relids, Relids top_parent_relids); - #endif /* PREP_H */ diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h index e07aaaf798..ac0a979010 100644 --- a/src/include/partitioning/partprune.h +++ b/src/include/partitioning/partprune.h @@ -76,7 +76,7 @@ extern PartitionPruneInfo *make_partition_pruneinfo(PlannerInfo *root, List *subpaths, List *partitioned_rels, List *prunequal); -extern Relids prune_append_rel_partitions(RelOptInfo *rel); +extern Bitmapset *prune_append_rel_partitions(RelOptInfo *rel); extern Bitmapset *get_matching_partitions(PartitionPruneContext *context, List *pruning_steps); diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 1f5378080d..73f684f992 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -5547,29 +5547,29 @@ select t1.b, ss.phv from join_ut1 t1 left join lateral (select t2.a as t2a, t3.a t3a, least(t1.a, t2.a, t3.a) phv from join_pt1 t2 join join_ut1 t3 on t2.a = t3.b) ss on t1.a = ss.t2a order by t1.a; - QUERY PLAN ------------------------------------------------------------------- + QUERY PLAN +-------------------------------------------------------------------- Sort - Output: t1.b, (LEAST(t1.a, t2.a, t3.a)), t1.a + Output: t1.b, (LEAST(t1.a, t2_1.a, t3.a)), t1.a Sort Key: t1.a -> Nested Loop Left Join - Output: t1.b, (LEAST(t1.a, t2.a, t3.a)), t1.a + Output: t1.b, (LEAST(t1.a, t2_1.a, t3.a)), t1.a -> Seq Scan on public.join_ut1 t1 Output: t1.a, t1.b, t1.c -> Hash Join - Output: t2.a, LEAST(t1.a, t2.a, t3.a) - Hash Cond: (t3.b = t2.a) + Output: t2_1.a, LEAST(t1.a, t2_1.a, t3.a) + Hash Cond: (t3.b = t2_1.a) -> Seq Scan on public.join_ut1 t3 Output: t3.a, t3.b, t3.c -> Hash - Output: t2.a + Output: t2_1.a -> Append - -> Seq Scan on public.join_pt1p1p1 t2 - Output: t2.a - Filter: (t1.a = t2.a) - -> Seq Scan on public.join_pt1p2 t2_1 + -> Seq Scan on public.join_pt1p1p1 t2_1 Output: t2_1.a Filter: (t1.a = t2_1.a) + -> Seq Scan on public.join_pt1p2 t2 + Output: t2.a + Filter: (t1.a = t2.a) (21 rows) select t1.b, ss.phv from join_ut1 t1 left join lateral diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 6bc106831e..1450cef057 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) -- 2.11.0
v5-0004-Move-append-expansion-code-into-its-own-file.patch (text/plain, 108.9K) download From 68c565448b11e4c1294411b1841a6d527bdb9726 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 7 Nov 2018 16:51:31 +0900 Subject: [PATCH v5 4/6] Move append expansion code into its own file This commit moves expand_append_rel and underlings to optimizer/utils/append.c. Also, all of the AppendRelInfo based expression manipulation routines are moved to optimizer/utils/appendinfo.c. This commit only moves the code and contains no functional changes. --- src/backend/optimizer/path/allpaths.c | 2 + src/backend/optimizer/path/equivclass.c | 1 + src/backend/optimizer/path/joinrels.c | 1 + src/backend/optimizer/plan/planner.c | 1 + src/backend/optimizer/prep/prepunion.c | 1516 ------------------------------- src/backend/optimizer/util/Makefile | 5 +- src/backend/optimizer/util/append.c | 719 +++++++++++++++ src/backend/optimizer/util/appendinfo.c | 851 +++++++++++++++++ src/backend/optimizer/util/pathnode.c | 1 + src/backend/optimizer/util/relnode.c | 1 + src/backend/partitioning/partprune.c | 1 + src/include/optimizer/append.h | 28 + src/include/optimizer/appendinfo.h | 43 + src/include/optimizer/prep.h | 21 - 14 files changed, 1652 insertions(+), 1539 deletions(-) create mode 100644 src/backend/optimizer/util/append.c create mode 100644 src/backend/optimizer/util/appendinfo.c create mode 100644 src/include/optimizer/append.h create mode 100644 src/include/optimizer/appendinfo.h diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 6ee800d84a..0d051681e6 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -30,6 +30,8 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "optimizer/append.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/geqo.h" diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index b22b36ec0e..5ace28eab0 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -22,6 +22,7 @@ #include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index da0831de4e..6e321ec9e7 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -15,6 +15,7 @@ #include "postgres.h" #include "miscadmin.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 84746a9bc9..216cadfba9 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -39,6 +39,7 @@ #include "nodes/print.h" #endif #include "nodes/relation.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index c6817757e7..70f37c593f 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -12,11 +12,6 @@ * case, but most of the heavy lifting for that is done elsewhere, * notably in prepjointree.c and allpaths.c. * - * There is also some code here to support planning of queries that use - * inheritance (SELECT FROM foo*). Inheritance trees are converted into - * append relations, and thenceforth share code with the UNION ALL case. - * - * * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -39,32 +34,21 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" -#include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" -#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" -#include "optimizer/var.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/selfuncs.h" #include "utils/syscache.h" -typedef struct -{ - PlannerInfo *root; - int nappinfos; - AppendRelInfo **appinfos; -} adjust_appendrel_attrs_context; - static RelOptInfo *recurse_set_operations(Node *setOp, PlannerInfo *root, List *colTypes, List *colCollations, bool junkOK, @@ -103,31 +87,6 @@ static List *generate_append_tlist(List *colTypes, List *colCollations, List *input_tlists, List *refnames_tlist); static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist); -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti, RelOptInfo *rel); -static void expand_partitioned_rtentry(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel); -static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel, - PlanRowMark *top_parentrc, Relation childrel); -static void make_inh_translation_list(TupleDesc old_tupdesc, - TupleDesc new_tupdesc, - Oid from_rel, Oid to_rel, - Index newvarno, List **translated_vars); -static RelOptInfo *build_append_child_rel(PlannerInfo *root, - RelOptInfo *parent, - Index childRTindex); -static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); -static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars); -static Node *adjust_appendrel_attrs_mutator(Node *node, - adjust_appendrel_attrs_context *context); -static Relids adjust_child_relids(Relids relids, int nappinfos, - AppendRelInfo **appinfos); -static List *adjust_inherited_tlist(List *tlist, - AppendRelInfo *context); /* @@ -1464,1478 +1423,3 @@ generate_setop_grouplist(SetOperationStmt *op, List *targetlist) Assert(lg == NULL); return grouplist; } - - -/* - * expand_append_rel - * This initializes RelOptInfos for an appendrel's child relations. - * - * 'rel' is the appendrel parent, whose range table entry ('rte') has been - * marked to require adding children. An appendrel parent could either - * be a subquery (if we flattened UNION ALL query) or a table that's known - * to have inheritance children. The latter consists of both regular - * inheritance parents and partitioned tables. - * - * For a subquery parent, there is not much to be done here because the - * children's RTEs are already present in the query, so we just initialize - * RelOptInfos for them. Also, the AppendRelInfos for child subqueries - * have already been added. - * - * For tables, we need to add the children to the range table and initialize - * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For - * a partitioned parent, we only add the children remaining after pruning. - * For regular inheritance parents, we find the children using - * find_all_inheritors and add all of them. - * - * If it turns out that there are no children, then we set rte->inh to false - * to let the caller know that only the parent table needs to be scanned. The - * caller can accordingly switch to a non-Append path. For a partitioned - * parent, that means an empty relation because parents themselves contain no - * data. - * - * For the regular inheritance case, the parent also gets another RTE with - * inh = false to represent it as an appendrel child. The original RTE is - * considered to represent the whole inheritance set. - */ -void -expand_append_rel(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, - Index rti) -{ - Assert(rte->inh); - /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ - Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); - - /* - * UNION ALL children already got RTEs and AppendRelInfos, so just build - * RelOptInfos and return. - * - * It might be a bit odd that this code is in this, because there is - * nothing to expand really. - */ - if (rte->rtekind == RTE_SUBQUERY) - { - ListCell *l; - - /* - * We don't need to use expand_planner_arrays in this case, because - * no new child RTEs are created. setup_simple_rel_arrays() and - * setup_append_rel_array would've considered these child RTEs when - * allocating space for various arrays. - */ - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst(l); - Index childRTindex = appinfo->child_relid; - - if (appinfo->parent_relid != rti) - continue; - - Assert(childRTindex < root->simple_rel_array_size); - Assert(root->simple_rte_array[childRTindex] != NULL); - - /* - * We set the correct value of baserestricinfo and - * baserestrict_min_security below. - */ - root->simple_rel_array[childRTindex] = - build_append_child_rel(root, rel, appinfo->child_relid); - } - } - else - { - Assert(rte->rtekind == RTE_RELATION); - Assert(has_subclass(rte->relid)); - - /* - * The rewriter should already have obtained an appropriate lock on - * each relation named in the query. However, for each child relation - * we add to the query, we must obtain an appropriate lock, because - * this will be the first use of those relations in the - * parse/rewrite/plan pipeline. Child rels should use the same - * lockmode as their parent. - */ - Assert(rte->rellockmode != NoLock); - - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - expand_partitioned_rtentry(root, rte, rti, rel); - else - expand_inherited_rtentry(root, rte, rti, rel); - } -} - -/* - * expand_inherited_rtentry - * Add entries for all the child tables to the query's rangetable, and - * build AppendRelInfo nodes for all the child tables and add them to - * root->append_rel_list. - * - * Note that the original RTE is considered to represent the whole - * inheritance set. The first of the generated RTEs is an RTE for the same - * table, but with inh = false, to represent the parent table in its role - * as a simple member of the inheritance set. - * - * A childless table is never considered to be an inheritance set. For - * regular inheritance, a parent RTE must always have at least two associated - * AppendRelInfos: one corresponding to the parent table as a simple member of - * inheritance set and one or more corresponding to the actual children. - */ -static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, - RelOptInfo *rel) -{ - Oid parentOID; - PlanRowMark *oldrc; - LOCKMODE lockmode = rte->rellockmode; - List *inhOIDs; - ListCell *l; - int num_children; - int num_children_added = 0; - - Assert(rte->rtekind == RTE_RELATION); - Assert(lockmode != NoLock); - parentOID = rte->relid; - - /* Scan for all members of inheritance set, acquire needed locks */ - inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); - - /* - * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite above has_subclass() check, if table - * once had a child but no longer does. - */ - num_children = list_length(inhOIDs); - if (num_children < 2) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks - * should've set isParent = true. We'll generate a new PlanRowMark for - * each child. - */ - oldrc = get_plan_rowmark(root->rowMarks, rti); - Assert(oldrc == NULL || oldrc->isParent); - - /* - * Must expand PlannerInfo arrays by num_children before we can add - * children. - */ - expand_planner_arrays(root, num_children); - - foreach(l, inhOIDs) - { - Oid childOID = lfirst_oid(l); - Relation newrelation; - RelOptInfo *childrel; - - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (RELATION_IS_OTHER_TEMP(newrelation)) - { - heap_close(newrelation, lockmode); - continue; - } - - childrel = add_inheritance_child_rel(root, rte, rti, rel, oldrc, - newrelation); - Assert(childrel != NULL); - - /* Close child relations, but keep locks */ - heap_close(newrelation, NoLock); - num_children_added++; - } - - /* - * If all children, including the parent (as child rel), were - * excluded, mark the parent rel as empty. If all the children were temp - * tables, pretend it's a non-inheritance situation; we don't need Append - * node in that case. The duplicate RTE we added for the parent table is - * harmless, so we don't bother to get rid of it; ditto for the useless - * PlanRowMark node. - */ - if (num_children_added == 0) - mark_dummy_rel(rel); - else if (num_children_added == 1) - rte->inh = false; - - /* - * Add junk columns needed by the row mark if any and also add the - * relevant expressions to the root parent's reltarget. - */ - if (oldrc) - { - List *tlist = add_rowmark_junk_columns(root, oldrc); - - build_base_rel_tlists(root, tlist); - } -} - -/* - * expand_partitioned_rtentry - * Prunes unnecessary partitions of a partitioned table and adds - * remaining ones to the Query and the PlannerInfo - * - * Partitions are added to the query in order in which they are found in - * the parent's PartitionDesc. - * - * Note: even though only the unpruned partitions will be added to the - * resulting plan, this still locks *all* partitions via find_all_inheritors - * in order to avoid partitions being locked in a different order than other - * places in the backend that may lock partitions. - */ -static void -expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel) -{ - LOCKMODE lockmode = parentrte->rellockmode; - PlanRowMark *rootrc = NULL; - int i; - Bitmapset *partindexes; - Index rootParentRTindex = parentrel->inh_root_parent > 0 ? - parentrel->inh_root_parent : - parentRTindex; - - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - - /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, values of the - * indexes of partitioned relations that appear down in the tree will be - * bubbled up into root parent's list so that when we've created Paths for - * all the children, the root table's list will contain all such indexes. - */ - parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - - /* Perform pruning. */ - partindexes = prune_append_rel_partitions(parentrel); - - /* Must expand PlannerInfo arrays before we can add children. */ - expand_planner_arrays(root, bms_num_members(partindexes)); - - /* - * For partitioned tables, we also store the partition RelOptInfo - * pointers in the parent's RelOptInfo. - */ - parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * - parentrel->nparts); - - rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); - Assert(rootrc == NULL || rootrc->isParent); - i = -1; - while ((i = bms_next_member(partindexes, i)) >= 0) - { - Oid childOID = parentrel->part_oids[i]; - Relation newrelation; - RelOptInfo *childrel; - - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); - Assert(!RELATION_IS_OTHER_TEMP(newrelation)); - - /* - * A partitioned child table with 0 children is a dummy rel, so don't - * bother creating planner objects for it. - */ - if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && - RelationGetPartitionDesc(newrelation)->nparts == 0) - { - heap_close(newrelation, NoLock); - continue; - } - - childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, - parentrel, rootrc, newrelation); - Assert(childrel != NULL); - parentrel->part_rels[i] = childrel; - - /* Close child relations, but keep locks */ - heap_close(newrelation, NoLock); - } - - /* - * Add junk columns needed by the row mark if any and also add the - * relevant expressions to the root parent's reltarget. - */ - if (rootrc) - { - List *tlist = add_rowmark_junk_columns(root, rootrc); - - build_base_rel_tlists(root, tlist); - } -} - -/* - * add_inheritance_child_rel - * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally - * a RelOptInfo for an inheritance child relation. - * - * The return value is the RelOptInfo that's added. - * - * PlanRowMarks still carry the top-parent's RTI, and the top-parent's - * allMarkTypes field still accumulates values from all descendents. - * - * "parentrte" and "parentRTindex" are immediate parent's RTE and - * RTI. "top_parentrc" is top parent's PlanRowMark. - */ -static RelOptInfo * -add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel, - PlanRowMark *top_parentrc, Relation childrel) -{ - Query *parse = root->parse; - Oid childOID = RelationGetRelid(childrel); - RangeTblEntry *childrte; - Index childRTindex; - AppendRelInfo *appinfo; - RelOptInfo *childrelopt; - - /* - * Build an RTE for the child, and attach to query's rangetable list. We - * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh appropriately. Also, set requiredPerms to zero - * since all required permissions checks are done on the original RTE. - * Likewise, set the child's securityQuals to empty, because we only want - * to apply the parent's RLS conditions regardless of what RLS properties - * individual children may have. (This is an intentional choice to make - * inherited RLS work like regular permissions checks.) The parent - * securityQuals will be propagated to children along with other base - * restriction clauses, so we don't need to do it here. - */ - childrte = copyObject(parentrte); - childrte->relid = childOID; - childrte->relkind = childrel->rd_rel->relkind; - /* - * A partitioned child will need to be expanded as an append parent - * itself, so set its inh to true. - */ - childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); - childrte->requiredPerms = 0; - childrte->securityQuals = NIL; - parse->rtable = lappend(parse->rtable, childrte); - childRTindex = list_length(parse->rtable); - - /* Create an AppendRelInfo and add it to planner's global list. */ - appinfo = make_append_rel_info(parentrel, parentrte, - childrel->rd_att, - childOID, - childrel->rd_rel->reltype, - childRTindex); - root->append_rel_list = lappend(root->append_rel_list, appinfo); - - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childrte->relid != parentrte->relid) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } - - /* - * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE. - */ - if (top_parentrc) - { - PlanRowMark *childrc = makeNode(PlanRowMark); - - childrc->rti = childRTindex; - childrc->prti = top_parentrc->rti; - childrc->rowmarkId = top_parentrc->rowmarkId; - /* Reselect rowmark type, because relkind might not match parent */ - childrc->markType = select_rowmark_type(childrte, - top_parentrc->strength); - childrc->allMarkTypes = (1 << childrc->markType); - childrc->strength = top_parentrc->strength; - childrc->waitPolicy = top_parentrc->waitPolicy; - - /* - * We mark RowMarks for partitioned child tables as parent RowMarks so - * that the executor ignores them (except their existence means that - * the child tables be locked using appropriate mode). - */ - childrc->isParent = (childrte->relkind == RELKIND_PARTITIONED_TABLE); - - /* Include child's rowmark type in top parent's allMarkTypes */ - top_parentrc->allMarkTypes |= childrc->allMarkTypes; - - root->rowMarks = lappend(root->rowMarks, childrc); - } - - /* - * Add the RelOptInfo. Even though we may not really scan this relation - * for reasons such as contradictory quals, we still need need to create - * one, because for every RTE in the query's range table, there must be an - * accompanying RelOptInfo. - */ - - /* First, store the RTE and appinfos into planner arrays. */ - Assert(root->simple_rte_array[childRTindex] == NULL); - root->simple_rte_array[childRTindex] = childrte; - Assert(root->append_rel_array[childRTindex] == NULL); - root->append_rel_array[childRTindex] = appinfo; - - childrelopt = build_append_child_rel(root, parentrel, childRTindex); - Assert(childrelopt != NULL); - - return childrelopt; -} - -/* - * make_inh_translation_list - * Build the list of translations from parent Vars to child Vars for - * an inheritance child. - * - * For paranoia's sake, we match type/collation as well as attribute name. - */ -static void -make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, - Oid from_rel, Oid to_rel, - Index newvarno, List **translated_vars) -{ - List *vars = NIL; - int oldnatts = old_tupdesc->natts; - int newnatts = new_tupdesc->natts; - int old_attno; - int new_attno = 0; - - for (old_attno = 0; old_attno < oldnatts; old_attno++) - { - Form_pg_attribute att; - char *attname; - Oid atttypid; - int32 atttypmod; - Oid attcollation; - - att = TupleDescAttr(old_tupdesc, old_attno); - if (att->attisdropped) - { - /* Just put NULL into this list entry */ - vars = lappend(vars, NULL); - continue; - } - attname = NameStr(att->attname); - atttypid = att->atttypid; - atttypmod = att->atttypmod; - attcollation = att->attcollation; - - /* - * When we are generating the "translation list" for the parent table - * of an inheritance set, no need to search for matches. - */ - if (from_rel == to_rel) - { - vars = lappend(vars, makeVar(newvarno, - (AttrNumber) (old_attno + 1), - atttypid, - atttypmod, - attcollation, - 0)); - continue; - } - - /* - * Otherwise we have to search for the matching column by name. - * There's no guarantee it'll have the same column position, because - * of cases like ALTER TABLE ADD COLUMN and multiple inheritance. - * However, in simple cases, the relative order of columns is mostly - * the same in both relations, so try the column of newrelation that - * follows immediately after the one that we just found, and if that - * fails, let syscache handle it. - */ - if (new_attno >= newnatts || - (att = TupleDescAttr(new_tupdesc, new_attno))->attisdropped || - strcmp(attname, NameStr(att->attname)) != 0) - { - HeapTuple newtup; - - newtup = SearchSysCacheAttName(to_rel, attname); - if (!newtup) - elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, get_rel_name(to_rel)); - new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; - ReleaseSysCache(newtup); - - att = TupleDescAttr(new_tupdesc, new_attno); - } - - /* Found it, check type and collation match */ - if (atttypid != att->atttypid || atttypmod != att->atttypmod) - elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, get_rel_name(to_rel)); - if (attcollation != att->attcollation) - elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, get_rel_name(to_rel)); - - vars = lappend(vars, makeVar(newvarno, - (AttrNumber) (new_attno + 1), - atttypid, - atttypmod, - attcollation, - 0)); - new_attno++; - } - - *translated_vars = vars; -} - -/* - * translate_col_privs - * Translate a bitmapset representing per-column privileges from the - * parent rel's attribute numbering to the child's. - * - * The only surprise here is that we don't translate a parent whole-row - * reference into a child whole-row reference. That would mean requiring - * permissions on all child columns, which is overly strict, since the - * query is really only going to reference the inherited columns. Instead - * we set the per-column bits for all inherited columns. - */ -static Bitmapset * -translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars) -{ - Bitmapset *child_privs = NULL; - bool whole_row; - int attno; - ListCell *lc; - - /* System attributes have the same numbers in all tables */ - for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) - { - if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - attno - FirstLowInvalidHeapAttributeNumber); - } - - /* Check if parent has whole-row reference */ - whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, - parent_privs); - - /* And now translate the regular user attributes, using the vars list */ - attno = InvalidAttrNumber; - foreach(lc, translated_vars) - { - Var *var = lfirst_node(Var, lc); - - attno++; - if (var == NULL) /* ignore dropped columns */ - continue; - if (whole_row || - bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - var->varattno - FirstLowInvalidHeapAttributeNumber); - } - - return child_privs; -} - -/* - * build_append_child_rel - * Build a RelOptInfo for child relation of an append rel - * - * After creating the RelOptInfo for the given child RT index, it goes on to - * initialize some of its fields base on the parent RelOptInfo. - * - * If the quals in baserestrictinfo turns out to be self-contradictory, the - * RelOptInfo is marked dummy before returning. - */ -static RelOptInfo * -build_append_child_rel(PlannerInfo *root, - RelOptInfo *parent, - Index childRTindex) -{ - RelOptInfo *childrel; - RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; - AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; - List *childquals; - ListCell *lc; - bool have_const_false_cq; - Index cq_min_security; - - /* Build the RelOptInfo. */ - childrel = build_simple_rel(root, childRTindex, parent); - - /* - * Propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - */ - childrel->direct_lateral_relids = parent->direct_lateral_relids; - childrel->lateral_relids = parent->lateral_relids; - childrel->lateral_referencers = parent->lateral_referencers; - - /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) - */ - childquals = false; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, parent->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual = (Node *) rinfo->clause; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, childqual, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might - * have securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals.) Pull any such securityQuals up into the - * baserestrictinfo for the child. This is similar to - * process_security_barrier_quals() for the parent rel, except - * that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* - * not likely that we'd see constants here, so no - * check - */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, - false, - false, - security_level, - NULL, NULL, - NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* Set child's version of baserestrictinfo. */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - } - - return childrel; -} - -/* - * add_rowmark_junk_columns - * Add necessary junk columns for rowmarked inheritance parent rel. - * - * These values are needed for locking of rels selected FOR UPDATE/SHARE, and - * to do EvalPlanQual rechecking. See comments for PlanRowMark in - * plannodes.h. - */ -static List * -add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) -{ - List *tlist = root->processed_tlist; - Var *var; - char resname[32]; - TargetEntry *tle; - - if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) - { - /* Need to fetch TID */ - var = makeVar(rc->rti, - SelfItemPointerAttributeNumber, - TIDOID, - -1, - InvalidOid, - 0); - snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - } - if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) - { - /* Need the whole row as a junk var */ - var = makeWholeRowVar(root->simple_rte_array[rc->rti], - rc->rti, - 0, - false); - snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - } - - /* For inheritance cases, always fetch the tableoid too. */ - var = makeVar(rc->rti, - TableOidAttributeNumber, - OIDOID, - -1, - InvalidOid, - 0); - snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - - return tlist; -} - -AppendRelInfo * -make_append_rel_info(RelOptInfo *parentrel, RangeTblEntry *parentrte, - TupleDesc childdesc, Oid childoid, Oid childreltype, - Index childRTindex) -{ - AppendRelInfo *appinfo = makeNode(AppendRelInfo); - - appinfo->parent_relid = parentrel->relid; - appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->reltype; - appinfo->child_reltype = childreltype; - make_inh_translation_list(parentrel->tupdesc, childdesc, - parentrte->relid, childoid, - childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = parentrte->relid; - - return appinfo; -} - -/* - * adjust_appendrel_attrs - * Copy the specified query or expression and translate Vars referring to a - * parent rel to refer to the corresponding child rel instead. We also - * update rtindexes appearing outside Vars, such as resultRelation and - * jointree relids. - * - * Note: this is only applied after conversion of sublinks to subplans, - * so we don't need to cope with recursion into sub-queries. - * - * Note: this is not hugely different from what pullup_replace_vars() does; - * maybe we should try to fold the two routines together. - */ -Node * -adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, - AppendRelInfo **appinfos) -{ - Node *result; - adjust_appendrel_attrs_context context; - - context.root = root; - context.nappinfos = nappinfos; - context.appinfos = appinfos; - - /* If there's nothing to adjust, don't call this function. */ - Assert(nappinfos >= 1 && appinfos != NULL); - - /* - * Must be prepared to start with a Query or a bare expression tree. - */ - if (node && IsA(node, Query)) - { - Query *newnode; - int cnt; - - newnode = query_tree_mutator((Query *) node, - adjust_appendrel_attrs_mutator, - (void *) &context, - QTW_IGNORE_RC_SUBQUERIES); - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (newnode->resultRelation == appinfo->parent_relid) - { - newnode->resultRelation = appinfo->child_relid; - /* Fix tlist resnos too, if it's inherited UPDATE */ - if (newnode->commandType == CMD_UPDATE) - newnode->targetList = - adjust_inherited_tlist(newnode->targetList, - appinfo); - break; - } - } - - result = (Node *) newnode; - } - else - result = adjust_appendrel_attrs_mutator(node, &context); - - return result; -} - -static Node * -adjust_appendrel_attrs_mutator(Node *node, - adjust_appendrel_attrs_context *context) -{ - AppendRelInfo **appinfos = context->appinfos; - int nappinfos = context->nappinfos; - int cnt; - - if (node == NULL) - return NULL; - if (IsA(node, Var)) - { - Var *var = (Var *) copyObject(node); - AppendRelInfo *appinfo = NULL; - - for (cnt = 0; cnt < nappinfos; cnt++) - { - if (var->varno == appinfos[cnt]->parent_relid) - { - appinfo = appinfos[cnt]; - break; - } - } - - if (var->varlevelsup == 0 && appinfo) - { - var->varno = appinfo->child_relid; - var->varnoold = appinfo->child_relid; - if (var->varattno > 0) - { - Node *newnode; - - if (var->varattno > list_length(appinfo->translated_vars)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - var->varattno, get_rel_name(appinfo->parent_reloid)); - newnode = copyObject(list_nth(appinfo->translated_vars, - var->varattno - 1)); - if (newnode == NULL) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - var->varattno, get_rel_name(appinfo->parent_reloid)); - return newnode; - } - else if (var->varattno == 0) - { - /* - * Whole-row Var: if we are dealing with named rowtypes, we - * can use a whole-row Var for the child table plus a coercion - * step to convert the tuple layout to the parent's rowtype. - * Otherwise we have to generate a RowExpr. - */ - if (OidIsValid(appinfo->child_reltype)) - { - Assert(var->vartype == appinfo->parent_reltype); - if (appinfo->parent_reltype != appinfo->child_reltype) - { - ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr); - - r->arg = (Expr *) var; - r->resulttype = appinfo->parent_reltype; - r->convertformat = COERCE_IMPLICIT_CAST; - r->location = -1; - /* Make sure the Var node has the right type ID, too */ - var->vartype = appinfo->child_reltype; - return (Node *) r; - } - } - else - { - /* - * Build a RowExpr containing the translated variables. - * - * In practice var->vartype will always be RECORDOID here, - * so we need to come up with some suitable column names. - * We use the parent RTE's column names. - * - * Note: we can't get here for inheritance cases, so there - * is no need to worry that translated_vars might contain - * some dummy NULLs. - */ - RowExpr *rowexpr; - List *fields; - RangeTblEntry *rte; - - rte = rt_fetch(appinfo->parent_relid, - context->root->parse->rtable); - fields = copyObject(appinfo->translated_vars); - rowexpr = makeNode(RowExpr); - rowexpr->args = fields; - rowexpr->row_typeid = var->vartype; - rowexpr->row_format = COERCE_IMPLICIT_CAST; - rowexpr->colnames = copyObject(rte->eref->colnames); - rowexpr->location = -1; - - return (Node *) rowexpr; - } - } - /* system attributes don't need any other translation */ - } - return (Node *) var; - } - if (IsA(node, CurrentOfExpr)) - { - CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node); - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (cexpr->cvarno == appinfo->parent_relid) - { - cexpr->cvarno = appinfo->child_relid; - break; - } - } - return (Node *) cexpr; - } - if (IsA(node, RangeTblRef)) - { - RangeTblRef *rtr = (RangeTblRef *) copyObject(node); - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (rtr->rtindex == appinfo->parent_relid) - { - rtr->rtindex = appinfo->child_relid; - break; - } - } - return (Node *) rtr; - } - if (IsA(node, JoinExpr)) - { - /* Copy the JoinExpr node with correct mutation of subnodes */ - JoinExpr *j; - AppendRelInfo *appinfo; - - j = (JoinExpr *) expression_tree_mutator(node, - adjust_appendrel_attrs_mutator, - (void *) context); - /* now fix JoinExpr's rtindex (probably never happens) */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - appinfo = appinfos[cnt]; - - if (j->rtindex == appinfo->parent_relid) - { - j->rtindex = appinfo->child_relid; - break; - } - } - return (Node *) j; - } - if (IsA(node, PlaceHolderVar)) - { - /* Copy the PlaceHolderVar node with correct mutation of subnodes */ - PlaceHolderVar *phv; - - phv = (PlaceHolderVar *) expression_tree_mutator(node, - adjust_appendrel_attrs_mutator, - (void *) context); - /* now fix PlaceHolderVar's relid sets */ - if (phv->phlevelsup == 0) - phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos, - context->appinfos); - return (Node *) phv; - } - - /* - * This is needed, because inheritance_make_rel_from_joinlist needs to - * translate root->join_info_list executing make_rel_from_joinlist for a - * given child. - */ - if (IsA(node, SpecialJoinInfo)) - { - SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; - SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); - - memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); - newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, - context->nappinfos, - context->appinfos); - newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, - context->nappinfos, - context->appinfos); - newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, - context->nappinfos, - context->appinfos); - newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, - context->nappinfos, - context->appinfos); - newinfo->semi_rhs_exprs = - (List *) expression_tree_mutator((Node *) - oldinfo->semi_rhs_exprs, - adjust_appendrel_attrs_mutator, - (void *) context); - return (Node *) newinfo; - } - - /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, AppendRelInfo)); - Assert(!IsA(node, PlaceHolderInfo)); - Assert(!IsA(node, MinMaxAggInfo)); - - /* - * We have to process RestrictInfo nodes specially. (Note: although - * set_append_rel_pathlist will hide RestrictInfos in the parent's - * baserestrictinfo list from us, it doesn't hide those in joininfo.) - */ - if (IsA(node, RestrictInfo)) - { - RestrictInfo *oldinfo = (RestrictInfo *) node; - RestrictInfo *newinfo = makeNode(RestrictInfo); - - /* Copy all flat-copiable fields */ - memcpy(newinfo, oldinfo, sizeof(RestrictInfo)); - - /* Recursively fix the clause itself */ - newinfo->clause = (Expr *) - adjust_appendrel_attrs_mutator((Node *) oldinfo->clause, context); - - /* and the modified version, if an OR clause */ - newinfo->orclause = (Expr *) - adjust_appendrel_attrs_mutator((Node *) oldinfo->orclause, context); - - /* adjust relid sets too */ - newinfo->clause_relids = adjust_child_relids(oldinfo->clause_relids, - context->nappinfos, - context->appinfos); - newinfo->required_relids = adjust_child_relids(oldinfo->required_relids, - context->nappinfos, - context->appinfos); - newinfo->outer_relids = adjust_child_relids(oldinfo->outer_relids, - context->nappinfos, - context->appinfos); - newinfo->nullable_relids = adjust_child_relids(oldinfo->nullable_relids, - context->nappinfos, - context->appinfos); - newinfo->left_relids = adjust_child_relids(oldinfo->left_relids, - context->nappinfos, - context->appinfos); - newinfo->right_relids = adjust_child_relids(oldinfo->right_relids, - context->nappinfos, - context->appinfos); - - /* - * Reset cached derivative fields, since these might need to have - * different values when considering the child relation. Note we - * don't reset left_ec/right_ec: each child variable is implicitly - * equivalent to its parent, so still a member of the same EC if any. - */ - newinfo->eval_cost.startup = -1; - newinfo->norm_selec = -1; - newinfo->outer_selec = -1; - newinfo->left_em = NULL; - newinfo->right_em = NULL; - newinfo->scansel_cache = NIL; - newinfo->left_bucketsize = -1; - newinfo->right_bucketsize = -1; - newinfo->left_mcvfreq = -1; - newinfo->right_mcvfreq = -1; - - return (Node *) newinfo; - } - - /* - * NOTE: we do not need to recurse into sublinks, because they should - * already have been converted to subplans before we see them. - */ - Assert(!IsA(node, SubLink)); - Assert(!IsA(node, Query)); - - return expression_tree_mutator(node, adjust_appendrel_attrs_mutator, - (void *) context); -} - -/* - * Substitute child relids for parent relids in a Relid set. The array of - * appinfos specifies the substitutions to be performed. - */ -static Relids -adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos) -{ - Bitmapset *result = NULL; - int cnt; - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - /* Remove parent, add child */ - if (bms_is_member(appinfo->parent_relid, relids)) - { - /* Make a copy if we are changing the set. */ - if (!result) - result = bms_copy(relids); - - result = bms_del_member(result, appinfo->parent_relid); - result = bms_add_member(result, appinfo->child_relid); - } - } - - /* If we made any changes, return the modified copy. */ - if (result) - return result; - - /* Otherwise, return the original set without modification. */ - return relids; -} - -/* - * Replace any relid present in top_parent_relids with its child in - * child_relids. Members of child_relids can be multiple levels below top - * parent in the partition hierarchy. - */ -Relids -adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, - Relids child_relids, Relids top_parent_relids) -{ - AppendRelInfo **appinfos; - int nappinfos; - Relids parent_relids = NULL; - Relids result; - Relids tmp_result = NULL; - int cnt; - - /* - * If the given relids set doesn't contain any of the top parent relids, - * it will remain unchanged. - */ - if (!bms_overlap(relids, top_parent_relids)) - return relids; - - appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); - - /* Construct relids set for the immediate parent of the given child. */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); - } - - /* Recurse if immediate parent is not the top parent. */ - if (!bms_equal(parent_relids, top_parent_relids)) - { - tmp_result = adjust_child_relids_multilevel(root, relids, - parent_relids, - top_parent_relids); - relids = tmp_result; - } - - result = adjust_child_relids(relids, nappinfos, appinfos); - - /* Free memory consumed by any intermediate result. */ - if (tmp_result) - bms_free(tmp_result); - bms_free(parent_relids); - pfree(appinfos); - - return result; -} - -/* - * Adjust the targetlist entries of an inherited UPDATE operation - * - * The expressions have already been fixed, but we have to make sure that - * the target resnos match the child table (they may not, in the case of - * a column that was added after-the-fact by ALTER TABLE). In some cases - * this can force us to re-order the tlist to preserve resno ordering. - * (We do all this work in special cases so that preptlist.c is fast for - * the typical case.) - * - * The given tlist has already been through expression_tree_mutator; - * therefore the TargetEntry nodes are fresh copies that it's okay to - * scribble on. - * - * Note that this is not needed for INSERT because INSERT isn't inheritable. - */ -static List * -adjust_inherited_tlist(List *tlist, AppendRelInfo *context) -{ - bool changed_it = false; - ListCell *tl; - List *new_tlist; - bool more; - int attrno; - - /* This should only happen for an inheritance case, not UNION ALL */ - Assert(OidIsValid(context->parent_reloid)); - - /* Scan tlist and update resnos to match attnums of child rel */ - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - Var *childvar; - - if (tle->resjunk) - continue; /* ignore junk items */ - - /* Look up the translation of this column: it must be a Var */ - if (tle->resno <= 0 || - tle->resno > list_length(context->translated_vars)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - tle->resno, get_rel_name(context->parent_reloid)); - childvar = (Var *) list_nth(context->translated_vars, tle->resno - 1); - if (childvar == NULL || !IsA(childvar, Var)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - tle->resno, get_rel_name(context->parent_reloid)); - - if (tle->resno != childvar->varattno) - { - tle->resno = childvar->varattno; - changed_it = true; - } - } - - /* - * If we changed anything, re-sort the tlist by resno, and make sure - * resjunk entries have resnos above the last real resno. The sort - * algorithm is a bit stupid, but for such a seldom-taken path, small is - * probably better than fast. - */ - if (!changed_it) - return tlist; - - new_tlist = NIL; - more = true; - for (attrno = 1; more; attrno++) - { - more = false; - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - - if (tle->resjunk) - continue; /* ignore junk items */ - - if (tle->resno == attrno) - new_tlist = lappend(new_tlist, tle); - else if (tle->resno > attrno) - more = true; - } - } - - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - - if (!tle->resjunk) - continue; /* here, ignore non-junk items */ - - tle->resno = attrno; - new_tlist = lappend(new_tlist, tle); - attrno++; - } - - return new_tlist; -} - -/* - * adjust_appendrel_attrs_multilevel - * Apply Var translations from a toplevel appendrel parent down to a child. - * - * In some cases we need to translate expressions referencing a parent relation - * to reference an appendrel child that's multiple levels removed from it. - */ -Node * -adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, - Relids child_relids, - Relids top_parent_relids) -{ - AppendRelInfo **appinfos; - Bitmapset *parent_relids = NULL; - int nappinfos; - int cnt; - - Assert(bms_num_members(child_relids) == bms_num_members(top_parent_relids)); - - appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); - - /* Construct relids set for the immediate parent of given child. */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); - } - - /* Recurse if immediate parent is not the top parent. */ - if (!bms_equal(parent_relids, top_parent_relids)) - node = adjust_appendrel_attrs_multilevel(root, node, parent_relids, - top_parent_relids); - - /* Now translate for this child */ - node = adjust_appendrel_attrs(root, node, nappinfos, appinfos); - - pfree(appinfos); - - return node; -} - -/* - * Construct the SpecialJoinInfo for a child-join by translating - * SpecialJoinInfo for the join between parents. left_relids and right_relids - * are the relids of left and right side of the join respectively. - */ -SpecialJoinInfo * -build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, - Relids left_relids, Relids right_relids) -{ - SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo); - AppendRelInfo **left_appinfos; - int left_nappinfos; - AppendRelInfo **right_appinfos; - int right_nappinfos; - - memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo)); - left_appinfos = find_appinfos_by_relids(root, left_relids, - &left_nappinfos); - right_appinfos = find_appinfos_by_relids(root, right_relids, - &right_nappinfos); - - sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand, - left_nappinfos, left_appinfos); - sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand, - right_nappinfos, - right_appinfos); - sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand, - left_nappinfos, left_appinfos); - sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand, - right_nappinfos, - right_appinfos); - sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root, - (Node *) sjinfo->semi_rhs_exprs, - right_nappinfos, - right_appinfos); - - pfree(left_appinfos); - pfree(right_appinfos); - - return sjinfo; -} - -/* - * find_appinfos_by_relids - * Find AppendRelInfo structures for all relations specified by relids. - * - * The AppendRelInfos are returned in an array, which can be pfree'd by the - * caller. *nappinfos is set to the number of entries in the array. - */ -AppendRelInfo ** -find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos) -{ - AppendRelInfo **appinfos; - int cnt = 0; - int i; - - *nappinfos = bms_num_members(relids); - appinfos = (AppendRelInfo **) palloc(sizeof(AppendRelInfo *) * *nappinfos); - - i = -1; - while ((i = bms_next_member(relids, i)) >= 0) - { - AppendRelInfo *appinfo = root->append_rel_array[i]; - - if (!appinfo) - elog(ERROR, "child rel %d not found in append_rel_array", i); - - appinfos[cnt++] = appinfo; - } - return appinfos; -} diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile index da9ccf32b4..d166030a10 100644 --- a/src/backend/optimizer/util/Makefile +++ b/src/backend/optimizer/util/Makefile @@ -12,7 +12,8 @@ subdir = src/backend/optimizer/util top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = clauses.o joininfo.o orclauses.o pathnode.o placeholder.o \ - plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o +OBJS = append.o appendinfo.o clauses.o joininfo.o orclauses.o pathnode.o \ + placeholder.o plancat.o predtest.o relnode.o restrictinfo.o tlist.o \ + var.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/optimizer/util/append.c b/src/backend/optimizer/util/append.c new file mode 100644 index 0000000000..bbc579817a --- /dev/null +++ b/src/backend/optimizer/util/append.c @@ -0,0 +1,719 @@ +/*------------------------------------------------------------------------- + * + * append.c + * Routines to process children of an appendrel parent + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/optimizer/path/append.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <limits.h> + +#include "access/sysattr.h" +#include "catalog/partition.h" +#include "catalog/pg_class.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_type.h" +#include "nodes/makefuncs.h" +#include "optimizer/append.h" +#include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" +#include "optimizer/plancat.h" +#include "optimizer/planmain.h" +#include "optimizer/planner.h" +#include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" +#include "optimizer/tlist.h" +#include "optimizer/var.h" +#include "partitioning/partprune.h" +#include "utils/rel.h" + +static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, + Index rti, RelOptInfo *rel); +static void expand_partitioned_rtentry(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel); +static RelOptInfo *build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); + + +/* + * expand_append_rel + * This initializes RelOptInfos for an appendrel's child relations. + * + * 'rel' is the appendrel parent, whose range table entry ('rte') has been + * marked to require adding children. An appendrel parent could either + * be a subquery (if we flattened UNION ALL query) or a table that's known + * to have inheritance children. The latter consists of both regular + * inheritance parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as an appendrel child. The original RTE is + * considered to represent the whole inheritance set. + */ +void +expand_append_rel(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, + Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_append_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_inherited_rtentry(root, rte, rti, rel); + } +} + +/* + * expand_inherited_rtentry + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. + * + * Note that the original RTE is considered to represent the whole + * inheritance set. The first of the generated RTEs is an RTE for the same + * table, but with inh = false, to represent the parent table in its role + * as a simple member of the inheritance set. + * + * A childless table is never considered to be an inheritance set. For + * regular inheritance, a parent RTE must always have at least two associated + * AppendRelInfos: one corresponding to the parent table as a simple member of + * inheritance set and one or more corresponding to the actual children. + */ +static void +expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, + RelOptInfo *rel) +{ + Oid parentOID; + PlanRowMark *oldrc; + LOCKMODE lockmode = rte->rellockmode; + List *inhOIDs; + ListCell *l; + int num_children; + int num_children_added = 0; + + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); + parentOID = rte->relid; + + /* Scan for all members of inheritance set, acquire needed locks */ + inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); + + /* + * Check that there's at least one descendant, else treat as no-child + * case. This could happen despite above has_subclass() check, if table + * once had a child but no longer does. + */ + num_children = list_length(inhOIDs); + if (num_children < 2) + { + /* Clear flag before returning */ + rte->inh = false; + return; + } + + /* + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. + */ + oldrc = get_plan_rowmark(root->rowMarks, rti); + Assert(oldrc == NULL || oldrc->isParent); + + /* + * Must expand PlannerInfo arrays by num_children before we can add + * children. + */ + expand_planner_arrays(root, num_children); + + foreach(l, inhOIDs) + { + Oid childOID = lfirst_oid(l); + Relation newrelation; + RelOptInfo *childrel; + + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + + /* + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. + */ + if (RELATION_IS_OTHER_TEMP(newrelation)) + { + heap_close(newrelation, lockmode); + continue; + } + + childrel = add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation); + Assert(childrel != NULL); + + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; + } + + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } +} + +/* + * expand_partitioned_rtentry + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * in order to avoid partitions being locked in a different order than other + * places in the backend that may lock partitions. + */ +static void +expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel) +{ + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; + int i; + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; + + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); + + /* + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. + */ + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); + + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + expand_planner_arrays(root, bms_num_members(partindexes)); + + /* + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. + */ + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); + + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) + { + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); + + /* + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. + */ + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } + + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; + + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + } + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); + } +} + +/* + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. + * + * The return value is the RelOptInfo that's added. + * + * PlanRowMarks still carry the top-parent's RTI, and the top-parent's + * allMarkTypes field still accumulates values from all descendents. + * + * "parentrte" and "parentRTindex" are immediate parent's RTE and + * RTI. "top_parentrc" is top parent's PlanRowMark. + */ +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel) +{ + Query *parse = root->parse; + Oid childOID = RelationGetRelid(childrel); + RangeTblEntry *childrte; + Index childRTindex; + AppendRelInfo *appinfo; + RelOptInfo *childrelopt; + + /* + * Build an RTE for the child, and attach to query's rangetable list. We + * copy most fields of the parent's RTE, but replace relation OID and + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties + * individual children may have. (This is an intentional choice to make + * inherited RLS work like regular permissions checks.) The parent + * securityQuals will be propagated to children along with other base + * restriction clauses, so we don't need to do it here. + */ + childrte = copyObject(parentrte); + childrte->relid = childOID; + childrte->relkind = childrel->rd_rel->relkind; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); + childrte->requiredPerms = 0; + childrte->securityQuals = NIL; + parse->rtable = lappend(parse->rtable, childrte); + childRTindex = list_length(parse->rtable); + + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + childrel->rd_att, + childOID, + childrel->rd_rel->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); + + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); + } + + /* + * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE. + */ + if (top_parentrc) + { + PlanRowMark *childrc = makeNode(PlanRowMark); + + childrc->rti = childRTindex; + childrc->prti = top_parentrc->rti; + childrc->rowmarkId = top_parentrc->rowmarkId; + /* Reselect rowmark type, because relkind might not match parent */ + childrc->markType = select_rowmark_type(childrte, + top_parentrc->strength); + childrc->allMarkTypes = (1 << childrc->markType); + childrc->strength = top_parentrc->strength; + childrc->waitPolicy = top_parentrc->waitPolicy; + + /* + * We mark RowMarks for partitioned child tables as parent RowMarks so + * that the executor ignores them (except their existence means that + * the child tables be locked using appropriate mode). + */ + childrc->isParent = (childrte->relkind == RELKIND_PARTITIONED_TABLE); + + /* Include child's rowmark type in top parent's allMarkTypes */ + top_parentrc->allMarkTypes |= childrc->allMarkTypes; + + root->rowMarks = lappend(root->rowMarks, childrc); + } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need need to create + * one, because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_append_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; +} + +/* + * build_append_child_rel + * Build a RelOptInfo for child relation of an append rel + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields base on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turns out to be self-contradictory, the + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + List *childquals; + ListCell *lc; + bool have_const_false_cq; + Index cq_min_security; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = false; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, parent->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual = (Node *) rinfo->clause; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, childqual, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might + * have securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals.) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except + * that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* + * not likely that we'd see constants here, so no + * check + */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, + false, + false, + security_level, + NULL, NULL, + NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* Set child's version of baserestrictinfo. */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; +} diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c new file mode 100644 index 0000000000..9502535e53 --- /dev/null +++ b/src/backend/optimizer/util/appendinfo.c @@ -0,0 +1,851 @@ +/*------------------------------------------------------------------------- + * + * appendinfo.c + * Routines for mapping between append parent(s) and children + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/optimizer/path/appendinfo.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" +#include "parser/parsetree.h" +#include "utils/rel.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" + +typedef struct +{ + PlannerInfo *root; + int nappinfos; + AppendRelInfo **appinfos; +} adjust_appendrel_attrs_context; + +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); +static Node *adjust_appendrel_attrs_mutator(Node *node, + adjust_appendrel_attrs_context *context); +static Relids adjust_child_relids(Relids relids, int nappinfos, + AppendRelInfo **appinfos); +static List *adjust_inherited_tlist(List *tlist, + AppendRelInfo *context); + +AppendRelInfo * +make_append_rel_info(RelOptInfo *parentrel, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex) +{ + AppendRelInfo *appinfo = makeNode(AppendRelInfo); + + appinfo->parent_relid = parentrel->relid; + appinfo->child_relid = childRTindex; + appinfo->parent_reltype = parentrel->reltype; + appinfo->child_reltype = childreltype; + make_inh_translation_list(parentrel->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, + &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; + + return appinfo; +} + +/* + * adjust_appendrel_attrs + * Copy the specified query or expression and translate Vars referring to a + * parent rel to refer to the corresponding child rel instead. We also + * update rtindexes appearing outside Vars, such as resultRelation and + * jointree relids. + * + * Note: this is only applied after conversion of sublinks to subplans, + * so we don't need to cope with recursion into sub-queries. + * + * Note: this is not hugely different from what pullup_replace_vars() does; + * maybe we should try to fold the two routines together. + */ +Node * +adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, + AppendRelInfo **appinfos) +{ + Node *result; + adjust_appendrel_attrs_context context; + + context.root = root; + context.nappinfos = nappinfos; + context.appinfos = appinfos; + + /* If there's nothing to adjust, don't call this function. */ + Assert(nappinfos >= 1 && appinfos != NULL); + + /* + * Must be prepared to start with a Query or a bare expression tree. + */ + if (node && IsA(node, Query)) + { + Query *newnode; + int cnt; + + newnode = query_tree_mutator((Query *) node, + adjust_appendrel_attrs_mutator, + (void *) &context, + QTW_IGNORE_RC_SUBQUERIES); + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (newnode->resultRelation == appinfo->parent_relid) + { + newnode->resultRelation = appinfo->child_relid; + /* Fix tlist resnos too, if it's inherited UPDATE */ + if (newnode->commandType == CMD_UPDATE) + newnode->targetList = + adjust_inherited_tlist(newnode->targetList, + appinfo); + break; + } + } + + result = (Node *) newnode; + } + else + result = adjust_appendrel_attrs_mutator(node, &context); + + return result; +} + +static Node * +adjust_appendrel_attrs_mutator(Node *node, + adjust_appendrel_attrs_context *context) +{ + AppendRelInfo **appinfos = context->appinfos; + int nappinfos = context->nappinfos; + int cnt; + + if (node == NULL) + return NULL; + if (IsA(node, Var)) + { + Var *var = (Var *) copyObject(node); + AppendRelInfo *appinfo = NULL; + + for (cnt = 0; cnt < nappinfos; cnt++) + { + if (var->varno == appinfos[cnt]->parent_relid) + { + appinfo = appinfos[cnt]; + break; + } + } + + if (var->varlevelsup == 0 && appinfo) + { + var->varno = appinfo->child_relid; + var->varnoold = appinfo->child_relid; + if (var->varattno > 0) + { + Node *newnode; + + if (var->varattno > list_length(appinfo->translated_vars)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + var->varattno, get_rel_name(appinfo->parent_reloid)); + newnode = copyObject(list_nth(appinfo->translated_vars, + var->varattno - 1)); + if (newnode == NULL) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + var->varattno, get_rel_name(appinfo->parent_reloid)); + return newnode; + } + else if (var->varattno == 0) + { + /* + * Whole-row Var: if we are dealing with named rowtypes, we + * can use a whole-row Var for the child table plus a coercion + * step to convert the tuple layout to the parent's rowtype. + * Otherwise we have to generate a RowExpr. + */ + if (OidIsValid(appinfo->child_reltype)) + { + Assert(var->vartype == appinfo->parent_reltype); + if (appinfo->parent_reltype != appinfo->child_reltype) + { + ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr); + + r->arg = (Expr *) var; + r->resulttype = appinfo->parent_reltype; + r->convertformat = COERCE_IMPLICIT_CAST; + r->location = -1; + /* Make sure the Var node has the right type ID, too */ + var->vartype = appinfo->child_reltype; + return (Node *) r; + } + } + else + { + /* + * Build a RowExpr containing the translated variables. + * + * In practice var->vartype will always be RECORDOID here, + * so we need to come up with some suitable column names. + * We use the parent RTE's column names. + * + * Note: we can't get here for inheritance cases, so there + * is no need to worry that translated_vars might contain + * some dummy NULLs. + */ + RowExpr *rowexpr; + List *fields; + RangeTblEntry *rte; + + rte = rt_fetch(appinfo->parent_relid, + context->root->parse->rtable); + fields = copyObject(appinfo->translated_vars); + rowexpr = makeNode(RowExpr); + rowexpr->args = fields; + rowexpr->row_typeid = var->vartype; + rowexpr->row_format = COERCE_IMPLICIT_CAST; + rowexpr->colnames = copyObject(rte->eref->colnames); + rowexpr->location = -1; + + return (Node *) rowexpr; + } + } + /* system attributes don't need any other translation */ + } + return (Node *) var; + } + if (IsA(node, CurrentOfExpr)) + { + CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node); + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (cexpr->cvarno == appinfo->parent_relid) + { + cexpr->cvarno = appinfo->child_relid; + break; + } + } + return (Node *) cexpr; + } + if (IsA(node, RangeTblRef)) + { + RangeTblRef *rtr = (RangeTblRef *) copyObject(node); + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (rtr->rtindex == appinfo->parent_relid) + { + rtr->rtindex = appinfo->child_relid; + break; + } + } + return (Node *) rtr; + } + if (IsA(node, JoinExpr)) + { + /* Copy the JoinExpr node with correct mutation of subnodes */ + JoinExpr *j; + AppendRelInfo *appinfo; + + j = (JoinExpr *) expression_tree_mutator(node, + adjust_appendrel_attrs_mutator, + (void *) context); + /* now fix JoinExpr's rtindex (probably never happens) */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + appinfo = appinfos[cnt]; + + if (j->rtindex == appinfo->parent_relid) + { + j->rtindex = appinfo->child_relid; + break; + } + } + return (Node *) j; + } + if (IsA(node, PlaceHolderVar)) + { + /* Copy the PlaceHolderVar node with correct mutation of subnodes */ + PlaceHolderVar *phv; + + phv = (PlaceHolderVar *) expression_tree_mutator(node, + adjust_appendrel_attrs_mutator, + (void *) context); + /* now fix PlaceHolderVar's relid sets */ + if (phv->phlevelsup == 0) + phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos, + context->appinfos); + return (Node *) phv; + } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + + /* Shouldn't need to handle planner auxiliary nodes here */ + Assert(!IsA(node, AppendRelInfo)); + Assert(!IsA(node, PlaceHolderInfo)); + Assert(!IsA(node, MinMaxAggInfo)); + + /* + * We have to process RestrictInfo nodes specially. (Note: although + * set_append_rel_pathlist will hide RestrictInfos in the parent's + * baserestrictinfo list from us, it doesn't hide those in joininfo.) + */ + if (IsA(node, RestrictInfo)) + { + RestrictInfo *oldinfo = (RestrictInfo *) node; + RestrictInfo *newinfo = makeNode(RestrictInfo); + + /* Copy all flat-copiable fields */ + memcpy(newinfo, oldinfo, sizeof(RestrictInfo)); + + /* Recursively fix the clause itself */ + newinfo->clause = (Expr *) + adjust_appendrel_attrs_mutator((Node *) oldinfo->clause, context); + + /* and the modified version, if an OR clause */ + newinfo->orclause = (Expr *) + adjust_appendrel_attrs_mutator((Node *) oldinfo->orclause, context); + + /* adjust relid sets too */ + newinfo->clause_relids = adjust_child_relids(oldinfo->clause_relids, + context->nappinfos, + context->appinfos); + newinfo->required_relids = adjust_child_relids(oldinfo->required_relids, + context->nappinfos, + context->appinfos); + newinfo->outer_relids = adjust_child_relids(oldinfo->outer_relids, + context->nappinfos, + context->appinfos); + newinfo->nullable_relids = adjust_child_relids(oldinfo->nullable_relids, + context->nappinfos, + context->appinfos); + newinfo->left_relids = adjust_child_relids(oldinfo->left_relids, + context->nappinfos, + context->appinfos); + newinfo->right_relids = adjust_child_relids(oldinfo->right_relids, + context->nappinfos, + context->appinfos); + + /* + * Reset cached derivative fields, since these might need to have + * different values when considering the child relation. Note we + * don't reset left_ec/right_ec: each child variable is implicitly + * equivalent to its parent, so still a member of the same EC if any. + */ + newinfo->eval_cost.startup = -1; + newinfo->norm_selec = -1; + newinfo->outer_selec = -1; + newinfo->left_em = NULL; + newinfo->right_em = NULL; + newinfo->scansel_cache = NIL; + newinfo->left_bucketsize = -1; + newinfo->right_bucketsize = -1; + newinfo->left_mcvfreq = -1; + newinfo->right_mcvfreq = -1; + + return (Node *) newinfo; + } + + /* + * NOTE: we do not need to recurse into sublinks, because they should + * already have been converted to subplans before we see them. + */ + Assert(!IsA(node, SubLink)); + Assert(!IsA(node, Query)); + + return expression_tree_mutator(node, adjust_appendrel_attrs_mutator, + (void *) context); +} + +/* + * Substitute child relids for parent relids in a Relid set. The array of + * appinfos specifies the substitutions to be performed. + */ +static Relids +adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos) +{ + Bitmapset *result = NULL; + int cnt; + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + /* Remove parent, add child */ + if (bms_is_member(appinfo->parent_relid, relids)) + { + /* Make a copy if we are changing the set. */ + if (!result) + result = bms_copy(relids); + + result = bms_del_member(result, appinfo->parent_relid); + result = bms_add_member(result, appinfo->child_relid); + } + } + + /* If we made any changes, return the modified copy. */ + if (result) + return result; + + /* Otherwise, return the original set without modification. */ + return relids; +} + +/* + * Replace any relid present in top_parent_relids with its child in + * child_relids. Members of child_relids can be multiple levels below top + * parent in the partition hierarchy. + */ +Relids +adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, + Relids child_relids, Relids top_parent_relids) +{ + AppendRelInfo **appinfos; + int nappinfos; + Relids parent_relids = NULL; + Relids result; + Relids tmp_result = NULL; + int cnt; + + /* + * If the given relids set doesn't contain any of the top parent relids, + * it will remain unchanged. + */ + if (!bms_overlap(relids, top_parent_relids)) + return relids; + + appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); + + /* Construct relids set for the immediate parent of the given child. */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); + } + + /* Recurse if immediate parent is not the top parent. */ + if (!bms_equal(parent_relids, top_parent_relids)) + { + tmp_result = adjust_child_relids_multilevel(root, relids, + parent_relids, + top_parent_relids); + relids = tmp_result; + } + + result = adjust_child_relids(relids, nappinfos, appinfos); + + /* Free memory consumed by any intermediate result. */ + if (tmp_result) + bms_free(tmp_result); + bms_free(parent_relids); + pfree(appinfos); + + return result; +} + +/* + * adjust_appendrel_attrs_multilevel + * Apply Var translations from a toplevel appendrel parent down to a child. + * + * In some cases we need to translate expressions referencing a parent relation + * to reference an appendrel child that's multiple levels removed from it. + */ +Node * +adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, + Relids child_relids, + Relids top_parent_relids) +{ + AppendRelInfo **appinfos; + Bitmapset *parent_relids = NULL; + int nappinfos; + int cnt; + + Assert(bms_num_members(child_relids) == bms_num_members(top_parent_relids)); + + appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); + + /* Construct relids set for the immediate parent of given child. */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); + } + + /* Recurse if immediate parent is not the top parent. */ + if (!bms_equal(parent_relids, top_parent_relids)) + node = adjust_appendrel_attrs_multilevel(root, node, parent_relids, + top_parent_relids); + + /* Now translate for this child */ + node = adjust_appendrel_attrs(root, node, nappinfos, appinfos); + + pfree(appinfos); + + return node; +} + +/* + * Construct the SpecialJoinInfo for a child-join by translating + * SpecialJoinInfo for the join between parents. left_relids and right_relids + * are the relids of left and right side of the join respectively. + */ +SpecialJoinInfo * +build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, + Relids left_relids, Relids right_relids) +{ + SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo); + AppendRelInfo **left_appinfos; + int left_nappinfos; + AppendRelInfo **right_appinfos; + int right_nappinfos; + + memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo)); + left_appinfos = find_appinfos_by_relids(root, left_relids, + &left_nappinfos); + right_appinfos = find_appinfos_by_relids(root, right_relids, + &right_nappinfos); + + sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand, + left_nappinfos, left_appinfos); + sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand, + right_nappinfos, + right_appinfos); + sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand, + left_nappinfos, left_appinfos); + sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand, + right_nappinfos, + right_appinfos); + sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root, + (Node *) sjinfo->semi_rhs_exprs, + right_nappinfos, + right_appinfos); + + pfree(left_appinfos); + pfree(right_appinfos); + + return sjinfo; +} + +/* + * find_appinfos_by_relids + * Find AppendRelInfo structures for all relations specified by relids. + * + * The AppendRelInfos are returned in an array, which can be pfree'd by the + * caller. *nappinfos is set to the number of entries in the array. + */ +AppendRelInfo ** +find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos) +{ + AppendRelInfo **appinfos; + int cnt = 0; + int i; + + *nappinfos = bms_num_members(relids); + appinfos = (AppendRelInfo **) palloc(sizeof(AppendRelInfo *) * *nappinfos); + + i = -1; + while ((i = bms_next_member(relids, i)) >= 0) + { + AppendRelInfo *appinfo = root->append_rel_array[i]; + + if (!appinfo) + elog(ERROR, "child rel %d not found in append_rel_array", i); + + appinfos[cnt++] = appinfo; + } + return appinfos; +} + +/* + * make_inh_translation_list + * Build the list of translations from parent Vars to child Vars for + * an inheritance child. + * + * For paranoia's sake, we match type/collation as well as attribute name. + */ +static void +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) +{ + List *vars = NIL; + int oldnatts = old_tupdesc->natts; + int newnatts = new_tupdesc->natts; + int old_attno; + int new_attno = 0; + + for (old_attno = 0; old_attno < oldnatts; old_attno++) + { + Form_pg_attribute att; + char *attname; + Oid atttypid; + int32 atttypmod; + Oid attcollation; + + att = TupleDescAttr(old_tupdesc, old_attno); + if (att->attisdropped) + { + /* Just put NULL into this list entry */ + vars = lappend(vars, NULL); + continue; + } + attname = NameStr(att->attname); + atttypid = att->atttypid; + atttypmod = att->atttypmod; + attcollation = att->attcollation; + + /* + * When we are generating the "translation list" for the parent table + * of an inheritance set, no need to search for matches. + */ + if (from_rel == to_rel) + { + vars = lappend(vars, makeVar(newvarno, + (AttrNumber) (old_attno + 1), + atttypid, + atttypmod, + attcollation, + 0)); + continue; + } + + /* + * Otherwise we have to search for the matching column by name. + * There's no guarantee it'll have the same column position, because + * of cases like ALTER TABLE ADD COLUMN and multiple inheritance. + * However, in simple cases, the relative order of columns is mostly + * the same in both relations, so try the column of newrelation that + * follows immediately after the one that we just found, and if that + * fails, let syscache handle it. + */ + if (new_attno >= newnatts || + (att = TupleDescAttr(new_tupdesc, new_attno))->attisdropped || + strcmp(attname, NameStr(att->attname)) != 0) + { + HeapTuple newtup; + + newtup = SearchSysCacheAttName(to_rel, attname); + if (!newtup) + elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", + attname, get_rel_name(to_rel)); + new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; + ReleaseSysCache(newtup); + + att = TupleDescAttr(new_tupdesc, new_attno); + } + + /* Found it, check type and collation match */ + if (atttypid != att->atttypid || atttypmod != att->atttypmod) + elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", + attname, get_rel_name(to_rel)); + if (attcollation != att->attcollation) + elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", + attname, get_rel_name(to_rel)); + + vars = lappend(vars, makeVar(newvarno, + (AttrNumber) (new_attno + 1), + atttypid, + atttypmod, + attcollation, + 0)); + new_attno++; + } + + *translated_vars = vars; +} + +/* + * translate_col_privs + * Translate a bitmapset representing per-column privileges from the + * parent rel's attribute numbering to the child's. + * + * The only surprise here is that we don't translate a parent whole-row + * reference into a child whole-row reference. That would mean requiring + * permissions on all child columns, which is overly strict, since the + * query is really only going to reference the inherited columns. Instead + * we set the per-column bits for all inherited columns. + */ +Bitmapset * +translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars) +{ + Bitmapset *child_privs = NULL; + bool whole_row; + int attno; + ListCell *lc; + + /* System attributes have the same numbers in all tables */ + for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) + { + if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + attno - FirstLowInvalidHeapAttributeNumber); + } + + /* Check if parent has whole-row reference */ + whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, + parent_privs); + + /* And now translate the regular user attributes, using the vars list */ + attno = InvalidAttrNumber; + foreach(lc, translated_vars) + { + Var *var = lfirst_node(Var, lc); + + attno++; + if (var == NULL) /* ignore dropped columns */ + continue; + if (whole_row || + bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + var->varattno - FirstLowInvalidHeapAttributeNumber); + } + + return child_privs; +} + +/* + * Adjust the targetlist entries of an inherited UPDATE operation + * + * The expressions have already been fixed, but we have to make sure that + * the target resnos match the child table (they may not, in the case of + * a column that was added after-the-fact by ALTER TABLE). In some cases + * this can force us to re-order the tlist to preserve resno ordering. + * (We do all this work in special cases so that preptlist.c is fast for + * the typical case.) + * + * The given tlist has already been through expression_tree_mutator; + * therefore the TargetEntry nodes are fresh copies that it's okay to + * scribble on. + * + * Note that this is not needed for INSERT because INSERT isn't inheritable. + */ +static List * +adjust_inherited_tlist(List *tlist, AppendRelInfo *context) +{ + bool changed_it = false; + ListCell *tl; + List *new_tlist; + bool more; + int attrno; + + /* This should only happen for an inheritance case, not UNION ALL */ + Assert(OidIsValid(context->parent_reloid)); + + /* Scan tlist and update resnos to match attnums of child rel */ + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + Var *childvar; + + if (tle->resjunk) + continue; /* ignore junk items */ + + /* Look up the translation of this column: it must be a Var */ + if (tle->resno <= 0 || + tle->resno > list_length(context->translated_vars)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + tle->resno, get_rel_name(context->parent_reloid)); + childvar = (Var *) list_nth(context->translated_vars, tle->resno - 1); + if (childvar == NULL || !IsA(childvar, Var)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + tle->resno, get_rel_name(context->parent_reloid)); + + if (tle->resno != childvar->varattno) + { + tle->resno = childvar->varattno; + changed_it = true; + } + } + + /* + * If we changed anything, re-sort the tlist by resno, and make sure + * resjunk entries have resnos above the last real resno. The sort + * algorithm is a bit stupid, but for such a seldom-taken path, small is + * probably better than fast. + */ + if (!changed_it) + return tlist; + + new_tlist = NIL; + more = true; + for (attrno = 1; more; attrno++) + { + more = false; + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + + if (tle->resjunk) + continue; /* ignore junk items */ + + if (tle->resno == attrno) + new_tlist = lappend(new_tlist, tle); + else if (tle->resno > attrno) + more = true; + } + } + + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + + if (!tle->resjunk) + continue; /* here, ignore non-junk items */ + + tle->resno = attrno; + new_tlist = lappend(new_tlist, tle); + attrno++; + } + + return new_tlist; +} diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index d50d86b252..8ce88876c4 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -20,6 +20,7 @@ #include "foreign/fdwapi.h" #include "nodes/extensible.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index c23db9d78d..52a11f434f 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -17,6 +17,7 @@ #include <limits.h> #include "miscadmin.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index bf17ae22ba..9120fcb0d3 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -44,6 +44,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/include/optimizer/append.h b/src/include/optimizer/append.h new file mode 100644 index 0000000000..5b44530279 --- /dev/null +++ b/src/include/optimizer/append.h @@ -0,0 +1,28 @@ +/*------------------------------------------------------------------------- + * + * append.h + * prototypes for append.c. + * + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/optimizer/append.h + * + *------------------------------------------------------------------------- + */ +#ifndef APPEND_H +#define APPEND_H + +#include "nodes/relation.h" + +/* + * append.c + * utilities for dealing with append relations + */ +extern void expand_append_rel(PlannerInfo *root, + RelOptInfo *rel, + RangeTblEntry *rte, + Index rti); + +#endif /* APPEND_H */ diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h new file mode 100644 index 0000000000..e205e78e6d --- /dev/null +++ b/src/include/optimizer/appendinfo.h @@ -0,0 +1,43 @@ +/*------------------------------------------------------------------------- + * + * appendinfo.h + * Routines for mapping expressions between append rel parent(s) and + * children + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/optimizer/appendinfo.h + * + *------------------------------------------------------------------------- + */ +#ifndef APPENDINFO_H +#define APPENDINFO_H + +#include "nodes/plannodes.h" +#include "nodes/relation.h" +#include "utils/relcache.h" + +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parentrel, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex); +extern Bitmapset *translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars); +extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, + int nappinfos, AppendRelInfo **appinfos); + +extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, + Relids child_relids, + Relids top_parent_relids); + +extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root, + Relids relids, int *nappinfos); + +extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, + SpecialJoinInfo *parent_sjinfo, + Relids left_relids, Relids right_relids); +extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, + Relids child_relids, Relids top_parent_relids); + +#endif /* APPENDINFO_H */ diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index e8e21e2ed5..0e2cdbc539 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -46,26 +46,5 @@ extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); * prototypes for prepunion.c */ extern RelOptInfo *plan_set_operations(PlannerInfo *root); -extern void expand_append_rel(PlannerInfo *root, - RelOptInfo *rel, - RangeTblEntry *rte, - Index rti); -extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, - int nappinfos, AppendRelInfo **appinfos); -extern AppendRelInfo *make_append_rel_info(RelOptInfo *parentrel, - RangeTblEntry *parentrte, - TupleDesc childdesc, Oid childoid, Oid childreltype, - Index childRTindex); -extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, - Relids child_relids, - Relids top_parent_relids); -extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root, - Relids relids, int *nappinfos); - -extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, - SpecialJoinInfo *parent_sjinfo, - Relids left_relids, Relids right_relids); -extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, - Relids child_relids, Relids top_parent_relids); #endif /* PREP_H */ -- 2.11.0
v5-0005-Teach-planner-to-only-process-unpruned-partitions.patch (text/plain, 7.2K) download From b869df16ad56eb7970085fe6cbf20cd0d5ab3be8 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 15 Oct 2018 10:59:24 +0900 Subject: [PATCH v5 5/6] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/append.c | 8 ++++++++ src/backend/optimizer/util/relnode.c | 3 +++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/relation.h | 2 ++ 6 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 6e321ec9e7..76531a05cf 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1424,6 +1424,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 216cadfba9..c60c491b61 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6766,7 +6766,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -6774,9 +6776,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -6857,7 +6857,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -6869,7 +6868,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -6879,9 +6880,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/append.c b/src/backend/optimizer/util/append.c index bbc579817a..2ca8e8d0ff 100644 --- a/src/backend/optimizer/util/append.c +++ b/src/backend/optimizer/util/append.c @@ -307,6 +307,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, /* Perform pruning. */ partindexes = prune_append_rel_partitions(parentrel); + parentrel->live_parts = partindexes; /* Must expand PlannerInfo arrays before we can add children. */ expand_planner_arrays(root, bms_num_members(partindexes)); @@ -347,6 +348,13 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, Assert(childrel != NULL); parentrel->part_rels[i] = childrel; + /* + * If partition is excluded by constraints, remove it from + * live_parts, too. + */ + if (IS_DUMMY_REL(childrel)) + parentrel->live_parts = bms_del_member(parentrel->live_parts, i); + /* Close child relations, but keep locks */ heap_close(newrelation, NoLock); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 52a11f434f..6c7eafe6db 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1748,6 +1748,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 9120fcb0d3..2ed5c9e8dd 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -441,29 +441,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 6b614e4794..7e5e4e2c6a 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -714,6 +714,8 @@ typedef struct RelOptInfo Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Set of live partitions; contains indexes + * into part_rels array */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.11.0
v5-0006-Do-not-lock-all-partitions-at-the-beginning.patch (text/plain, 1.2K) download From 289fff1a50deead4b2b743d62056b613a916c221 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 12 Oct 2018 19:12:53 +0900 Subject: [PATCH v5 6/6] Do not lock all partitions at the beginning --- src/backend/optimizer/util/append.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/backend/optimizer/util/append.c b/src/backend/optimizer/util/append.c index 2ca8e8d0ff..f1627aef33 100644 --- a/src/backend/optimizer/util/append.c +++ b/src/backend/optimizer/util/append.c @@ -291,10 +291,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, parentrel->inh_root_parent : parentRTindex; - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - /* * Initialize partitioned_child_rels to contain this RT index. * @@ -328,8 +324,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, Relation newrelation; RelOptInfo *childrel; - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); + newrelation = heap_open(childOID, lockmode); Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-11-09 17:43  Jesper Pedersen <jesper.pedersen@redhat.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 0 replies; 265+ messages in thread

From: Jesper Pedersen @ 2018-11-09 17:43 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers; David Rowley <david.rowley@2ndquadrant.com>

Hi Amit,

On 11/9/18 3:55 AM, Amit Langote wrote:
> And here are patches structured that way.  I've addressed some of the
> comments posted by Imai-san upthread.  Also, since David's patch to
> postpone PlannerInfo.total_pages calculation went into the tree earlier
> this week, it's no longer included in this set.
> 

Thanks for doing the split this way. The patch passes check-world.

I ran a SELECT test using hash partitions, and got

        Master    v5
64:    6k        59k
1024:  283       59k

The non-partitioned case gives 77k. The difference in TPS between the 
partition case vs. the non-partitioned case comes down to 
set_plain_rel_size() vs. set_append_rel_size() under 
set_base_rel_sizes(); flamegraphs for this sent off-list.

Best regards,
  Jesper




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-11-10 11:59  David Rowley <david.rowley@2ndquadrant.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 1 reply; 265+ messages in thread

From: David Rowley @ 2018-11-10 11:59 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers

On 9 November 2018 at 21:55, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
> v5-0001-Store-inheritance-root-parent-index-in-otherrel-s.patch
>
>   Adds a inh_root_parent field that's set in inheritance child otherrel
>   RelOptInfos to store the RT index of their root parent
>
> v5-0002-Overhaul-inheritance-update-delete-planning.patch
>
>   Patch that adjusts planner so that inheritance_planner can use partition
>   pruning.

I've started looking at these two, but only so far made it through
0001 and 0002.

Here's what I noted down during the review.

0001:

1. Why do we need the new field that this patch adds? I see in 0002
it's used like:

+ if (childrel->inh_root_parent > 0 &&
+ childrel->inh_root_parent == root->parse->resultRelation)

Would something like...
int relid;
if (childrel->part_schema == NULL &&
bms_get_singleton_member(childrel->top_parent_relids, &relid) &&
relid == root->parse->resultRelation)

...not do the trick?

0002:

2. What's wrong with childrel->relids?

+ child_relids = bms_make_singleton(appinfo->child_relid);

3. Why not use childrel->top_parent_relids?

+ top_parent_relids = bms_make_singleton(childrel->inh_root_parent);

4. The following comment in inheritance_make_rel_from_joinlist()
implies that the function will not be called for SELECT, but the
comment above the function does not mention that.

/*
* For UPDATE/DELETE queries, the top parent can only ever be a table.
* As a contrast, it could be a UNION ALL subquery in the case of SELECT.
*/
Assert(planner_rt_fetch(top_parent_relid, root)->rtekind == RTE_RELATION);

5. Should the following comment talk about "Sub-partitioned tables"
rather than "sub-partitions"?

+ * Sub-partitions have to be processed recursively, because
+ * AppendRelInfos link sub-partitions to their immediate parents, not
+ * the root partitioned table.

6. Can't you just pass childrel->relids and
childrel->top_parent_relids instead of making new ones?

+ child_relids = bms_make_singleton(appinfo->child_relid);
+ Assert(childrel->inh_root_parent > 0);
+ top_parent_relids = bms_make_singleton(childrel->inh_root_parent);
+ translated_joinlist = (List *)
+ adjust_appendrel_attrs_multilevel(root,
+   (Node *) joinlist,
+   child_relids,
+   top_parent_relids);

7. I'm just wondering what your idea is here?

+ /* Reset join planning specific data structures. */
+ root->join_rel_list = NIL;
+ root->join_rel_hash = NULL;

Is there a reason to nullify these? You're not releasing any memory
and the new structures that will be built won't overlap with the ones
built last round. I don't mean to imply that the code is wrong, it
just does not appear to be particularly right.

8. In regards to:

+ * NB: Do we need to change the child EC members to be marked
+ * as non-child somehow?
+ */
+ childrel->reloptkind = RELOPT_BASEREL;

I know we talked a bit about this before, but this time I put together
a crude patch that runs some code each time we skip an em_is_child ==
true EquivalenceMember. The code checks if any of the em_relids are
RELOPT_BASEREL. What I'm looking for here are places where we
erroneously skip the member when we shouldn't.  Running the regression
tests with this patch in place shows a number of problems.  Likely I
should only trigger the warning when bms_membership(em->em_relids) ==
BMS_SINGLETON, but it never-the-less appears to highlight various
possible issues. Applying the same on master only appears to show the
cases where em->em_relids isn't a singleton set.  I've attached the
patch to let you see what I mean.

-- 
 David Rowley                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

verify_em_child.diff (application/octet-stream, 11.6K) download diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 5ace28eab0..7212f2d3bc 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -675,7 +675,10 @@ get_eclass_for_sort_expr(PlannerInfo *root, */ if (cur_em->em_is_child && !bms_equal(cur_em->em_relids, rel)) + { + verify_em_child(root, cur_em); continue; + } /* * If below an outer join, don't match constants: they're not as @@ -2002,7 +2005,10 @@ exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2) EquivalenceMember *em = (EquivalenceMember *) lfirst(lc2); if (em->em_is_child) + { + verify_em_child(root, em); continue; /* ignore children here */ + } if (equal(item1, em->em_expr)) item1member = true; else if (equal(item2, em->em_expr)) @@ -2059,7 +2065,10 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root, Var *var; if (em->em_is_child) + { + verify_em_child(root, em); continue; /* ignore children here */ + } /* EM must be a Var, possibly with RelabelType */ var = (Var *) em->em_expr; @@ -2286,7 +2295,10 @@ generate_implied_equalities_for_column(PlannerInfo *root, RestrictInfo *rinfo; if (other_em->em_is_child) + { + verify_em_child(root, other_em); continue; /* ignore children here */ + } /* Make sure it'll be a join to a different rel */ if (other_em == cur_em || @@ -2386,6 +2398,21 @@ have_relevant_eclass_joinclause(PlannerInfo *root, return false; } +void +verify_em_child(PlannerInfo *root, EquivalenceMember *em) +{ + int relid; + Assert(em->em_is_child); + + relid = -1; + while ((relid = bms_next_member(em->em_relids, relid)) >= 0) + { + if (root->simple_rel_array[relid]->reloptkind == RELOPT_BASEREL) + elog(WARNING, "child EquivalenceMember skipped: %s", nodeToString(em->em_expr)); + } +} + + /* * has_relevant_eclass_joinclause @@ -2476,7 +2503,10 @@ eclass_useful_for_merging(PlannerInfo *root, EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc); if (cur_em->em_is_child) + { + verify_em_child(root, cur_em); continue; /* ignore children here */ + } if (!bms_overlap(cur_em->em_relids, relids)) return true; diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c index ec66cb9c3c..27bff4a56d 100644 --- a/src/backend/optimizer/path/pathkeys.c +++ b/src/backend/optimizer/path/pathkeys.c @@ -717,7 +717,10 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel, ListCell *k; if (sub_member->em_is_child) + { + verify_em_child(rel->subroot, sub_member); continue; /* ignore children here */ + } foreach(k, subquery_tlist) { @@ -1179,6 +1182,8 @@ select_outer_pathkeys_for_merge(PlannerInfo *root, if (!em->em_is_const && !em->em_is_child && !bms_overlap(em->em_relids, joinrel->relids)) score++; + else if (em->em_is_child) + verify_em_child(root, em); } ecs[necs] = oeclass; diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 28c4b53fea..56a7c69775 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -237,7 +237,7 @@ static MergeJoin *make_mergejoin(List *tlist, static Sort *make_sort(Plan *lefttree, int numCols, AttrNumber *sortColIdx, Oid *sortOperators, Oid *collations, bool *nullsFirst); -static Plan *prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys, +static Plan *prepare_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, Relids relids, const AttrNumber *reqColIdx, bool adjust_tlist_in_place, @@ -246,10 +246,10 @@ static Plan *prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Oid **p_sortOperators, Oid **p_collations, bool **p_nullsFirst); -static EquivalenceMember *find_ec_member_for_tle(EquivalenceClass *ec, +static EquivalenceMember *find_ec_member_for_tle(PlannerInfo *root,EquivalenceClass *ec, TargetEntry *tle, Relids relids); -static Sort *make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, +static Sort *make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, Relids relids); static Sort *make_sort_from_groupcols(List *groupcls, AttrNumber *grpColIdx, @@ -266,7 +266,7 @@ static Group *make_group(List *tlist, List *qual, int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators, Plan *lefttree); static Unique *make_unique_from_sortclauses(Plan *lefttree, List *distinctList); -static Unique *make_unique_from_pathkeys(Plan *lefttree, +static Unique *make_unique_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, int numCols); static Gather *make_gather(List *qptlist, List *qpqual, int nworkers, int rescan_param, bool single_copy, Plan *subplan); @@ -1147,7 +1147,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path) plan->righttree = NULL; /* Compute sort column info, and adjust MergeAppend's tlist as needed */ - (void) prepare_sort_from_pathkeys(plan, pathkeys, + (void) prepare_sort_from_pathkeys(root, plan, pathkeys, best_path->path.parent->relids, NULL, true, @@ -1177,7 +1177,7 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path) subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST); /* Compute sort column info, and adjust subplan's tlist as needed */ - subplan = prepare_sort_from_pathkeys(subplan, pathkeys, + subplan = prepare_sort_from_pathkeys(root, subplan, pathkeys, subpath->parent->relids, node->sortColIdx, false, @@ -1606,7 +1606,7 @@ create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path) Assert(pathkeys != NIL); /* Compute sort column info, and adjust subplan's tlist as needed */ - subplan = prepare_sort_from_pathkeys(subplan, pathkeys, + subplan = prepare_sort_from_pathkeys(root, subplan, pathkeys, best_path->subpath->parent->relids, gm_plan->sortColIdx, false, @@ -1788,7 +1788,7 @@ create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags) * relids. Thus, if this sort path is based on a child relation, we must * pass its relids. */ - plan = make_sort_from_pathkeys(subplan, best_path->path.pathkeys, + plan = make_sort_from_pathkeys(root, subplan, best_path->path.pathkeys, IS_OTHER_REL(best_path->subpath->parent) ? best_path->path.parent->relids : NULL); @@ -1853,7 +1853,7 @@ create_upper_unique_plan(PlannerInfo *root, UpperUniquePath *best_path, int flag subplan = create_plan_recurse(root, best_path->subpath, flags | CP_LABEL_TLIST); - plan = make_unique_from_pathkeys(subplan, + plan = make_unique_from_pathkeys(root, subplan, best_path->path.pathkeys, best_path->numkeys); @@ -3879,7 +3879,7 @@ create_mergejoin_plan(PlannerInfo *root, if (best_path->outersortkeys) { Relids outer_relids = outer_path->parent->relids; - Sort *sort = make_sort_from_pathkeys(outer_plan, + Sort *sort = make_sort_from_pathkeys(root, outer_plan, best_path->outersortkeys, outer_relids); @@ -3893,7 +3893,7 @@ create_mergejoin_plan(PlannerInfo *root, if (best_path->innersortkeys) { Relids inner_relids = inner_path->parent->relids; - Sort *sort = make_sort_from_pathkeys(inner_plan, + Sort *sort = make_sort_from_pathkeys(root, inner_plan, best_path->innersortkeys, inner_relids); @@ -5604,7 +5604,7 @@ make_sort(Plan *lefttree, int numCols, * or a Result stacked atop lefttree). */ static Plan * -prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys, +prepare_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, Relids relids, const AttrNumber *reqColIdx, bool adjust_tlist_in_place, @@ -5671,7 +5671,7 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys, tle = get_tle_by_resno(tlist, reqColIdx[numsortkeys]); if (tle) { - em = find_ec_member_for_tle(ec, tle, relids); + em = find_ec_member_for_tle(root, ec, tle, relids); if (em) { /* found expr at right place in tlist */ @@ -5702,7 +5702,7 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys, foreach(j, tlist) { tle = (TargetEntry *) lfirst(j); - em = find_ec_member_for_tle(ec, tle, relids); + em = find_ec_member_for_tle(root, ec, tle, relids); if (em) { /* found expr already in tlist */ @@ -5745,7 +5745,10 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys, */ if (em->em_is_child && !bms_is_subset(em->em_relids, relids)) + { + verify_em_child(root, em); continue; + } sortexpr = em->em_expr; exprvars = pull_var_clause((Node *) sortexpr, @@ -5831,7 +5834,7 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys, * Child EC members are ignored unless they belong to given 'relids'. */ static EquivalenceMember * -find_ec_member_for_tle(EquivalenceClass *ec, +find_ec_member_for_tle(PlannerInfo *root, EquivalenceClass *ec, TargetEntry *tle, Relids relids) { @@ -5860,7 +5863,10 @@ find_ec_member_for_tle(EquivalenceClass *ec, */ if (em->em_is_child && !bms_is_subset(em->em_relids, relids)) + { + verify_em_child(root, em); continue; + } /* Match if same expression (after stripping relabel) */ emexpr = em->em_expr; @@ -5883,7 +5889,7 @@ find_ec_member_for_tle(EquivalenceClass *ec, * 'relids' is the set of relations required by prepare_sort_from_pathkeys() */ static Sort * -make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids) +make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, Relids relids) { int numsortkeys; AttrNumber *sortColIdx; @@ -5892,7 +5898,7 @@ make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids) bool *nullsFirst; /* Compute sort column info, and adjust lefttree as needed */ - lefttree = prepare_sort_from_pathkeys(lefttree, pathkeys, + lefttree = prepare_sort_from_pathkeys(root, lefttree, pathkeys, relids, NULL, false, @@ -6204,7 +6210,7 @@ make_unique_from_sortclauses(Plan *lefttree, List *distinctList) * as above, but use pathkeys to identify the sort columns and semantics */ static Unique * -make_unique_from_pathkeys(Plan *lefttree, List *pathkeys, int numCols) +make_unique_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, int numCols) { Unique *node = makeNode(Unique); Plan *plan = &node->plan; @@ -6265,7 +6271,7 @@ make_unique_from_pathkeys(Plan *lefttree, List *pathkeys, int numCols) foreach(j, plan->targetlist) { tle = (TargetEntry *) lfirst(j); - em = find_ec_member_for_tle(ec, tle, NULL); + em = find_ec_member_for_tle(root, ec, tle, NULL); if (em) { /* found expr already in tlist */ diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index cafde307ad..a3f34a5c8c 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -169,6 +169,7 @@ extern List *generate_implied_equalities_for_column(PlannerInfo *root, Relids prohibited_rels); extern bool have_relevant_eclass_joinclause(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2); +extern void verify_em_child(PlannerInfo *root, EquivalenceMember *em); extern bool has_relevant_eclass_joinclause(PlannerInfo *root, RelOptInfo *rel1); extern bool eclass_useful_for_merging(PlannerInfo *root,
^ permalink raw reply [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2018-11-12 04:35  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 0 replies; 265+ messages in thread

From: Imai, Yoshikazu @ 2018-11-12 04:35 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers; David Rowley <david.rowley@2ndquadrant.com>

Hi Amit,

On Thu, Nov 8, 2018 at 8:26 PM, Amit Langote wrote:
> On 2018/11/07 10:00, Imai, Yoshikazu wrote:
> > About inheritance_make_rel_from_joinlist(), I considered how it processes
> > joins for sub-partitioned-table.
> > 
> > sub-partitioned-table image:
> > part
> >   sub1
> >     leaf1
> >     leaf2
> > 
> > inheritance_make_rel_from_joinlist() translates join_list and join_info_list
> > for each leafs(leaf1, leaf2 in above image). To translate those two lists for
> > leaf1, inheritance_make_rel_from_joinlist() translates lists from part to sub1
> > and nextly from sub1 to leaf1. For leaf2, inheritance_make_rel_from_joinlist() 
> > translates lists from part to sub1 and from sub1 to leaf2. There are same
> > translation from part to sub1, and I think it is better if we can eliminate it.
> > I attached 0002-delta.patch.
> > 
> > In the patch, inheritance_make_rel_from_joinlist() translates lists not only for
> > leafs but for mid-nodes, in a depth-first manner, so it can use lists of
> > mid-nodes for translating lists from mid-node to leafs, which eliminates same
> > translation.
> 
> I don't think the translation happens twice for the same leaf partitions.
> 
> Applying adjust_appendrel_attrs_*multilevel* for only leaf nodes, as
> happens with the current code, is same as first translating using
> adjust_appendrel_attrs from part to sub1 and then from sub1 to leaf1 and
> leaf2 during recursion with sub1 as the parent.

Thanks for replying.
I interpreted your thoughts about translation as below.

adjust_appendrel_attrs_multilevel for leaf1: root -> sub1 -> leaf1
adjust_appendrel_attrs_multilevel for leaf2: sub1(produced at above) -> leaf2

But I wonder translation of leaf2 actually reuses the results of sub1 which is
produced at leaf1 translation. ISTM translation for leaf1, leaf2 are executed
as below.

adjust_appendrel_attrs_multilevel for leaf1: root -> sub1 -> leaf1
adjust_appendrel_attrs_multilevel for leaf2: root -> sub1 -> leaf2


> > I think it might be better if we can apply same logic to inheritance_planner(),
> > which once implemented the same logic as below. 
> > 
> > 
> >     foreach(lc, root->append_rel_list)
> >     {
> >         AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
> >         ...
> >         /*
> >          * expand_inherited_rtentry() always processes a parent before any of
> >          * that parent's children, so the parent_root for this relation should
> >          * already be available.
> >          */
> >         parent_root = parent_roots[appinfo->parent_relid];
> >         Assert(parent_root != NULL);
> >         parent_parse = parent_root->parse;
> >         ...
> >         subroot->parse = (Query *)
> >             adjust_appendrel_attrs(parent_root,
> >                                    (Node *) parent_parse,
> >                                    1, &appinfo);
> 
> Actually, inheritance_planner is also using
> adjust_appendrel_attrs_multilevel.  I'm not sure if you're looking at the
> latest (10/26) patch.

Sorry for my poor explanation. I described the above code as old code which is
not patch applied. 


Since it is difficult to explain my thoughts with words, I will show the
performance degration case.

Partition tables are below two sets.

Set1:
[create 800 partitions directly under root]
CREATE TABLE rt (a int, b int) PARTITION BY RANGE (a);
\o /dev/null
SELECT 'CREATE TABLE leaf' || x::text || ' PARTITION OF rt FOR VALUES FROM ('
|| (x)::text || ') TO (' || (x+1)::text || ');' FROM generate_series(0, 800) x;
\gexec
\o

Set2:
[create 800 partitions under a partitioned table which is under root]
CREATE TABLE rt (a int, b int) PARTITION BY RANGE (a);
CREATE TABLE sub1 PARTITION OF rt FOR VALUES FROM (0) TO (100) PARTITION BY
RANGE (b);
\o /dev/null
SELECT 'CREATE TABLE leaf' || x::text || ' PARTITION OF sub1 FOR VALUES FROM ('
|| (x)::text || ') TO (' || (x+1)::text || ');' FROM generate_series(0, 800) x;
\gexec
\o


Create a generic plan of updation or deletion.

[create a delete generic plan]
set plan_cache_mode = 'force_generic_plan';
prepare delete_stmt(int) as delete from rt where b = $1;
execute delete_stmt(1);


In creating generic plan, paths/plans for all partitions are created because
we don't know which plan is used before "EXECUTE" command happens.
In creating paths in inheritance_planner(),
adjust_appendrel_attrs()/adjust_appendrel_attrs_multilevel() is executed many
times and it allocates a lot of memory in total if there are many partitions.

How amount of memory is used with above tests is...

without v5 patches, Set1: 242MB
without v5 patches, Set2: 247MB
with v5 patches, Set1: 420MB
with v5 patches, Set2: 820MB
# Thanks for supplying v5 patches :)

Without sub-partition(Set1), memory allocated by adjust_appendrel_attrs() with
v5 patches is 1.7x larger than without v5 patches. With sub-partition(Set2),
memory allocated by adjust_appendrel_attrs() with v5 patches is 3.3x larger
than without v5 patches.
I think why memory usage in "with v5 patches, Set2" is almost 2 times than
"with v5 patches, Set1" is that adjust_appendrel_attrs() from root to sub1 is
occurred at each translation for each partitions in "with v5 patches, Set2".

Currently, a query to a partition table is processed faster by a custom plan
than by a generic plan, so we would not use a generic plan that I don't know
whether we should solve this large memory allocation problem.

--
Yoshikazu Imai 



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-11-14 10:28  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: David Rowley <david.rowley@2ndquadrant.com>
  0 siblings, 2 replies; 265+ messages in thread

From: Amit Langote @ 2018-11-14 10:28 UTC (permalink / raw)
  To: David Rowley <david.rowley@2ndquadrant.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers

David, Imai-san,

Thanks for reviewing.  I've included both replies in this email so that I
can attach the latest patch as well.

On 2018/11/10 20:59, David Rowley wrote:
> I've started looking at these two, but only so far made it through
> 0001 and 0002.
> 
> Here's what I noted down during the review.
> 
> 0001:
> 
> 1. Why do we need the new field that this patch adds? I see in 0002
> it's used like:
> 
> + if (childrel->inh_root_parent > 0 &&
> + childrel->inh_root_parent == root->parse->resultRelation)
> 
> Would something like...
> int relid;
> if (childrel->part_schema == NULL &&
> bms_get_singleton_member(childrel->top_parent_relids, &relid) &&
> relid == root->parse->resultRelation)
> 
> ...not do the trick?

Actually, that's one way and earlier patches relied on that, but it gets a
bit ugly given that it's not always the top_parent_relid we're looking for
in the partitioning/inheritance specific code.  Consider this:

select *
from (select a from parted_table p union all
      select a from inherited_table i) s
where s.a = 1;

top_parent_relids refers to the union all parent subquery 's RT index,
which makes the partitioning/inheritance code scramble through a chain of
parent relation relids to figure out the RT index of the table in the query.

So, inh_root_parent is useful to distinguish the inheritance root parent
from the Append relation root parent, without much code.

That said, in the latest version, I've modified the UPDATE/DELETE planning
patch such that it doesn't use inh_root_parent (or the new code doesn't
need to refer to root parent where it previously did), so I've moved the
patch that adds inh_root_parent to the 2nd in the series.

> 0002:
> 
> 2. What's wrong with childrel->relids?
> 
> + child_relids = bms_make_singleton(appinfo->child_relid);

I've modified the code to not have to use child_relids.

> 3. Why not use childrel->top_parent_relids?
> 
> + top_parent_relids = bms_make_singleton(childrel->inh_root_parent);

Ditto.

> 4. The following comment in inheritance_make_rel_from_joinlist()
> implies that the function will not be called for SELECT, but the
> comment above the function does not mention that.
> 
> /*
> * For UPDATE/DELETE queries, the top parent can only ever be a table.
> * As a contrast, it could be a UNION ALL subquery in the case of SELECT.
> */
> Assert(planner_rt_fetch(top_parent_relid, root)->rtekind == RTE_RELATION);

Fixed the function's header comment to be clear.

> 5. Should the following comment talk about "Sub-partitioned tables"
> rather than "sub-partitions"?
> 
> + * Sub-partitions have to be processed recursively, because
> + * AppendRelInfos link sub-partitions to their immediate parents, not
> + * the root partitioned table.

Okay, done.

> 6. Can't you just pass childrel->relids and
> childrel->top_parent_relids instead of making new ones?
> 
> + child_relids = bms_make_singleton(appinfo->child_relid);
> + Assert(childrel->inh_root_parent > 0);
> + top_parent_relids = bms_make_singleton(childrel->inh_root_parent);
> + translated_joinlist = (List *)
> + adjust_appendrel_attrs_multilevel(root,
> +   (Node *) joinlist,
> +   child_relids,
> +   top_parent_relids);

The new code uses adjust_appendrel_attrs, so those Relids variables are
not needed.

> 7. I'm just wondering what your idea is here?
> 
> + /* Reset join planning specific data structures. */
> + root->join_rel_list = NIL;
> + root->join_rel_hash = NULL;
> 
> Is there a reason to nullify these? You're not releasing any memory
> and the new structures that will be built won't overlap with the ones
> built last round. I don't mean to imply that the code is wrong, it
> just does not appear to be particularly right.

In initial versions of the patch, the same top-level PlannerInfo was used
when calling make_rel_from_joinlist for all child tables.  So,
join_rel_hash built for a given child would be assumed to be valid for the
next which isn't true, because joinrels would be different across children.

In the latest patch, inheritance_make_rel_from_joinlist uses different
PlannerInfos for different child target relations, so the above problem
doesn't exist.  IOW, you won't see above two lines in the latest patch.

> 8. In regards to:
> 
> + * NB: Do we need to change the child EC members to be marked
> + * as non-child somehow?
> + */
> + childrel->reloptkind = RELOPT_BASEREL;
> 
> I know we talked a bit about this before, but this time I put together
> a crude patch that runs some code each time we skip an em_is_child ==
> true EquivalenceMember. The code checks if any of the em_relids are
> RELOPT_BASEREL. What I'm looking for here are places where we
> erroneously skip the member when we shouldn't.  Running the regression
> tests with this patch in place shows a number of problems.  Likely I
> should only trigger the warning when bms_membership(em->em_relids) ==
> BMS_SINGLETON, but it never-the-less appears to highlight various
> possible issues. Applying the same on master only appears to show the
> cases where em->em_relids isn't a singleton set.  I've attached the
> patch to let you see what I mean.

Thanks for this.  I've been thinking about what to do about it, but
haven't decided what's that yet.  Please let me spend some more time
thinking on it.  AFAICT, dealing with this will ensure that join planning
against target child relations can use EC-based optimizations, but it's
not incorrect as is per se.

On 2018/11/12 13:35, Imai, Yoshikazu wrote:
> adjust_appendrel_attrs_multilevel for leaf1: root -> sub1 -> leaf1
> adjust_appendrel_attrs_multilevel for leaf2: root -> sub1 -> leaf2

Ah, I see what you mean.

The root -> sub1 translation will be repeated for each leaf partition if
done via adjust_appendrel_attrs_multilevel.  On the other hand, if we
could do the root to sub1 translation once and pass it to the recursive
call using sub1 as the parent.

I've changed the patch use adjust_appendrel_attrs.

> Since it is difficult to explain my thoughts with words, I will show the
> performance degration case.
>
> Partition tables are below two sets.

[ ... ]

> Create a generic plan of updation or deletion.
>
> [create a delete generic plan]
> set plan_cache_mode = 'force_generic_plan';
> prepare delete_stmt(int) as delete from rt where b = $1;
> execute delete_stmt(1);

[ ... ]

> How amount of memory is used with above tests is...
>
> without v5 patches, Set1: 242MB
> without v5 patches, Set2: 247MB
> with v5 patches, Set1: 420MB
> with v5 patches, Set2: 820MB

Although I didn't aim to fix planning for the generic plan case where no
pruning occurs, the above result is not acceptable.  That is, the new
implementation of inheritance update/delete planning shouldn't consume
more memory than the previous.  In fact, it should've consumed less,
because the patch claims that it gets rid of redundant processing per
partition.

I understood why update/delete planning consumed more memory with the
patch.  It was due to a problem with the patch that modifies inheritance
update/delete planning.  The exact problem was that the query tree would
be translated (hence copied) *twice* for every partition!  First during
query planning where the query tree would be translated to figure out a
targetlist for partitions and then again before calling grouping_planner.
Also, the adjust_appendrel_attrs_multilevel made it worse for multi-level
partitioning case, because of repeated copying for root to intermediate
partitioned tables, as Imai-san pointed out.

I've fixed that making sure that query tree is translated only once and
saved for later steps to use.  Imai-san, please check the memory
consumption with the latest patch.

Attached updated patches.  Significant revisions are as follows (note that
I reversed the order of 0001 and 0002):

v6-0001-Overhaul-inheritance-update-delete-planning.patch

The major changes is fixing the problem above.

v6-0002-Store-inheritance-root-parent-index-in-otherrel-s.patch

No change.

v6-0003-Lazy-creation-of-RTEs-for-inheritance-children.patch

Made inheritance expansion a separate step of make_one_rel whereas
previously it would be invoked at the beginning of set_append_rel_size.
Now, it runs just before set_base_rel_sizes.  The same step also
recursively expands (and performs pruning for) any child partitioned
tables that were added by the expansion of partitioned tables originally
mentioned in the query.  With this change, we don't need to worry about
the range table changing as set_base_rel_size is executing, which could
lead to problems.

v6-0004-Move-append-expansion-code-into-its-own-file.patch
v6-0005-Teach-planner-to-only-process-unpruned-partitions.patch
v6-0006-Do-not-lock-all-partitions-at-the-beginning.patch

No change.

Thanks,
Amit

Attachments: download all (6 files, .tar.gz)

v6-0001-Overhaul-inheritance-update-delete-planning.patch (text/plain, 51.5K) download From 495e3b0b8ff1373c3653e5f7b7e3095a4b54e315 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 11:18:12 +0900 Subject: [PATCH v6 1/6] Overhaul inheritance update/delete planning With the current method, inheritance_planner, which handles the planning for update/delete commands targeting inheritance trees, performs the *whole* planning for each child table. That involves translating the query tree to set a given child table in place of the original inheritance root table as the query's target table, followed by calling grouping_planner on the translated query, which performs query planning and additional steps needed to apply correct target list based on the child target table. That's inefficient for two reasons: 1. partprune.c cannot be used to perform partition pruning, because it can only be invoked from query_planner. With the current approach, query_planner only sees the individual partitions in the query tree it receives, not the parent partitioned table. This leaves each partition to be pruned using constraint exclusion. (constraint exclusion cannon prune hash partitions which means UPDATE/DELETE don't support hash partition pruning.) 2. Repeated invocation of query_planner results in allocating large amounts of memory, especially if there are many child tables. Also, it involves repeatedly performing the same processing, such as jointree processing. This commit addresses both of the issues by rewriting portions of inheritance_planner and adding some supporting functionality to allpath.c. With the new implementation, inheritance_planner calls query_planner only once at the beginning with the original unmodified query, which creates the access paths of individual relations after pruning any unnecessary partitions. For each target child relation, it also creates a sub-PartitionInfo containing translated version of the query and a targetlist suitable for the child. Child PlannerInfos are saved in the top PlannerInfo for using later. If the query involves join against the target relation, join paths are created for each target child relation by replacing the original target table in the join tree by a given child table. Join relations (RelOptInfos thereof) for all target child relations are collected in a global list in the top PlannerInfo. After creating the join paths for all target child relations, inheritance_planner calls grouping_planner() on each child join relation using the previously created child PlannerInfo to finish up the paths such that they produce query's top-level target list expanded according to a given child relation's descriptor. grouping_planner()'s interface is modified so that we can pass it what's called a 'planned_rel', a RelOptInfo that already contains the necessary paths needed to produce its output. This removes some existing code in inheritance_planner that dealt with any subquery RTEs in the query. The rationale of that code was that the subquery RTEs may change during each iteration of planning (that is, for different children), so different iterations better use different copies of those RTEs. That was handled by making fresh copies of those RTEs for each iteration of planning which were appended to the range table, accompanied by modifying all expressions and auxiliary structures that referenced the original subquery RTEs to instead reference the copies (that is, change the varnos). This copying meant we would end up adding S * N new entries to the original range table by the time we got to the last unpruned child, where S is the number of subquery RTEs in the original query and N the number of unpruned children. Since with the new code we perform planning just once, I think we don't need this special handling. Actually, there is a regression test output change due to no longer having copies of subquery RTEs (see the diff of partition_join.out file.) Also we no longer need the hack in relation_excluded_by_constraints() involving setting PlannerInfo.inhTargetKind to enable constraint exclusion for target child relation, which is no longer is no longer needed. Constraint exclusion runs during query_planner step described above. Regression test output change in partition_join.out is due to the fact that we no longer create duplicates of subquery RTEs in child range tables. --- doc/src/sgml/ddl.sgml | 14 +- src/backend/nodes/outfuncs.c | 1 - src/backend/optimizer/path/allpaths.c | 307 ++++++++++++++++++- src/backend/optimizer/plan/createplan.c | 10 - src/backend/optimizer/plan/planner.c | 431 +++++++-------------------- src/backend/optimizer/prep/prepjointree.c | 1 - src/backend/optimizer/prep/prepunion.c | 33 +- src/backend/optimizer/util/plancat.c | 50 +--- src/include/nodes/relation.h | 31 +- src/test/regress/expected/partition_join.out | 4 +- 10 files changed, 475 insertions(+), 407 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index c8268222af..ca7359c4a4 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -3935,15 +3935,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <note> <para> - Currently, pruning of partitions during the planning of an - <command>UPDATE</command> or <command>DELETE</command> command is - implemented using the constraint exclusion method (however, it is - controlled by the <literal>enable_partition_pruning</literal> rather than - <literal>constraint_exclusion</literal>) &mdash; see the following section - for details and caveats that apply. - </para> - - <para> Execution-time partition pruning currently only occurs for the <literal>Append</literal> and <literal>MergeAppend</literal> node types. It is not yet implemented for the <literal>ModifyTable</literal> node @@ -3966,9 +3957,8 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <para> <firstterm>Constraint exclusion</firstterm> is a query optimization - technique similar to partition pruning. While it is primarily used - for partitioning implemented using the legacy inheritance method, it can be - used for other purposes, including with declarative partitioning. + technique similar to partition pruning. It is primarily used + for partitioning implemented using the legacy inheritance method. </para> <para> diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index f0c396530d..08657fcfd9 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2309,7 +2309,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); WRITE_UINT_FIELD(qual_security_level); - WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind); WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasDeletedRTEs); diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 738bb30848..7787592870 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -36,6 +36,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" @@ -93,6 +94,9 @@ static void set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); +static PlannerInfo *adjust_inherit_target_child(PlannerInfo *root, + RelOptInfo *childrel, + AppendRelInfo *appinfo); static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, @@ -119,6 +123,8 @@ static void set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); +static void inheritance_make_rel_from_joinlist(PlannerInfo *root, + List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, pushdown_safety_info *safetyInfo); static bool recurse_pushdown_safe(Node *setOp, Query *topquery, @@ -217,13 +223,40 @@ make_one_rel(PlannerInfo *root, List *joinlist) /* * Generate access paths for the entire join tree. + * + * For UPDATE/DELETE on an inheritance parent, join paths should be + * generated for each child result rel separately. */ - rel = make_rel_from_joinlist(root, joinlist); + if (root->parse->resultRelation && + root->simple_rte_array[root->parse->resultRelation]->inh) + { + inheritance_make_rel_from_joinlist(root, joinlist); - /* - * The result should join all and only the query's base rels. - */ - Assert(bms_equal(rel->relids, root->all_baserels)); + /* + * There should be as many child source rels as there are child + * subroots. + */ + Assert(list_length(root->inh_target_child_roots) == + list_length(root->inh_target_child_rels)); + + /* + * Return the RelOptInfo of original target relation, although this + * doesn't really contain the final path. inheritance_planner + * from where we got here will generate the final path, but not + * through this RelOptInfo. + */ + rel = find_base_rel(root, root->parse->resultRelation); + } + else + { + rel = make_rel_from_joinlist(root, joinlist); + + /* + * The result should join all and only the query's base rels. + */ + Assert(bms_equal(rel->relids, root->all_baserels)); + + } return rel; } @@ -1001,6 +1034,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, ListCell *parentvars; ListCell *childvars; ListCell *lc; + PlannerInfo *subroot = root; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1219,9 +1253,17 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, set_rel_consider_parallel(root, childrel, childRTE); /* - * Compute the child's size. + * If the parent is the result relation, we need a reltarget for the + * child relation that will be suitable to use the child also as the + * target relation. */ - set_rel_size(root, childrel, childRTindex, childRTE); + if (appinfo->parent_relid == root->parse->resultRelation) + subroot = adjust_inherit_target_child(root, childrel, appinfo); + + /* + * Compute the child's size using possibly modified subroot. + */ + set_rel_size(subroot, childrel, childRTindex, childRTE); /* * It is possible that constraint exclusion detected a contradiction @@ -1231,6 +1273,26 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, if (IS_DUMMY_REL(childrel)) continue; + /* + * If we modified subroot for the target inheritance case, add it + * to root->inh_target_child_roots. + */ + if (subroot != root) + { + root->inh_target_child_roots = + lappend(root->inh_target_child_roots, subroot); + + /* + * If the childrel itself was a partitioned table, its children + * would've been added into subroot. Copy relevant fields + * into the parent root. + */ + if (subroot->inh_target_child_roots != NIL) + root->inh_target_child_roots = + list_concat(root->inh_target_child_roots, + subroot->inh_target_child_roots); + } + /* We have at least one live child. */ has_live_children = true; @@ -1327,6 +1389,109 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, } /* + * adjust_inherit_target_child + * For an inheritance target child relation, this adjusts its + * reltarget so that it contains expressions suitable for processing as + * a target relation, creates a child PlannerInfo containing translated + * copy of the query and returns it. + * + * The child PlannerInfo reuses most of the parent PlannerInfo's fields + * unchanged, except unexpanded_tlist and processed_tlist are based on the + * child relation. + */ +static PlannerInfo * +adjust_inherit_target_child(PlannerInfo *root, RelOptInfo *childrel, + AppendRelInfo *appinfo) +{ + PlannerInfo *subroot; + List *tlist; + List *translated_exprs; + ListCell *lc2; + + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + + /* + * We'd like to build the reltarget afresh; save the translated + * version of parent's expressions aside. + */ + translated_exprs = childrel->reltarget->exprs; + childrel->reltarget->exprs = NIL; + + /* Translate the original query's expressions to this child. */ + subroot = makeNode(PlannerInfo); + memcpy(subroot, root, sizeof(PlannerInfo)); + + /* + * Restore the unexpanded tlist for translation, so that child's + * query contains targetList numbered (resnos) per its own + * TupleDesc, which adjust_inherited_tlist ensures. + */ + root->parse->targetList = root->unexpanded_tlist; + subroot->parse = (Query *) adjust_appendrel_attrs(root, + (Node *) root->parse, + 1, &appinfo); + + /* + * Save subroot's targetlist so that childrel's own children can use it as + * unexpanded tlist. Must copy because subroot->parse->targetList will + * be modified soon. + */ + subroot->unexpanded_tlist = list_copy(subroot->parse->targetList); + + /* + * Apply planner's expansion of targetlist, such as adding various junk + * column, filling placeholder entries for dropped columns, etc., all of + * which occurs with the child's TupleDesc. + */ + tlist = preprocess_targetlist(subroot); + subroot->processed_tlist = tlist; + build_base_rel_tlists(subroot, tlist); + + /* + * Some of the expressions in parent's reltarget might not be in the + * child's freshly built reltarget expressions, because the latter only + * contains those attributes that are needed to be present in the top- + * level tlist (or ones that preprocess_targetlist thinks are needed to + * be in the tlist.) We may need other attributes such as those that + * are required for computing WHERE clauses, which are already computed + * for the parent during deconstruct_jointree processing of the original + * query. We've already got a translated copy of those attributes, from + * which pick only those that are not already present. + */ + foreach(lc2, translated_exprs) + { + Expr *expr = lfirst(lc2); + + if (!list_member(childrel->reltarget->exprs, expr)) + childrel->reltarget->exprs = lappend(childrel->reltarget->exprs, + expr); + } + + /* + * Set a few other fields of subroot. + * + * Reset inh_target_child_roots to not be same as parent root's so that + * the subroots for this child's own children (if any) don't end up in + * root parent's list. We'll eventually merge all entries into one list, + * but that's now now. + */ + subroot->inh_target_child_roots = NIL; + + /* + * Adjust all_baserels to replace the original target relation with the + * child target relation. Copy it before modifying though. + */ + subroot->all_baserels = bms_copy(root->all_baserels); + subroot->all_baserels = bms_del_member(subroot->all_baserels, + root->parse->resultRelation); + subroot->all_baserels = bms_add_member(subroot->all_baserels, + subroot->parse->resultRelation); + + return subroot; +} + +/* * set_append_rel_pathlist * Build access paths for an "append relation" */ @@ -2624,6 +2789,134 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows) } /* + * inheritance_make_rel_from_joinlist + * Perform join planning for all non-dummy leaf inheritance children + * in their role as an UPDATE/DELETE query's target relation + * + * If a child relation is a partitioned table, its children are processed in + * turn by recursively calling this function. + */ +static void +inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; +#ifdef USE_ASSERT_CHECKING + Relids all_baserels; +#endif + + /* + * For UPDATE/DELETE queries, the top parent can only ever be a table. + * As a contrast, it could be a UNION ALL subquery in the case of SELECT. + */ + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + Assert(planner_rt_fetch(resultRelation, root)->rtekind == RTE_RELATION); + + /* Nothing to do. */ + if (IS_DUMMY_REL(find_base_rel(root, resultRelation ))) + return; + + foreach(lc, root->inh_target_child_roots) + { + PlannerInfo *subroot = lfirst(lc); + RelOptInfo *childrel; + AppendRelInfo *appinfo; + List *translated_joinlist; + + Assert(subroot->parse->resultRelation > 0); + childrel = find_base_rel(root, subroot->parse->resultRelation); + appinfo = root->append_rel_array[subroot->parse->resultRelation]; + + if (appinfo->parent_relid != root->parse->resultRelation) + continue; + + /* Ignore excluded/pruned children. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* + * Modify joinlist such that relations joined to the top parent rel + * appear to be joined to the child rel instead. Do the same for + * any SpecialJoinInfo structs. + */ + translated_joinlist = (List *) + adjust_appendrel_attrs(subroot, + (Node *) joinlist, + 1, &appinfo); + subroot->join_info_list = (List *) + adjust_appendrel_attrs(subroot, + (Node *) root->join_info_list, + 1, &appinfo); + + /* + * Sub-partitioned tables have to be processed recursively using the + * translated subroot as the parent, because AppendRelInfos link + * sub-partitions to their immediate parents, not the root partitioned + * table. + */ + if (childrel->part_scheme != NULL) + { + inheritance_make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * Add this child relation as a placeholder in the parent root's + * inh_target_child_rels so that inheritance_planner see same + * number of entries as inh_target_child_roots. + */ + root->inh_target_child_rels = + lappend(root->inh_target_child_rels, childrel); + + /* Also propagate this child's own children into parent's list. */ + if (subroot->inh_target_child_rels != NIL) + root->inh_target_child_rels = + list_concat(root->inh_target_child_rels, + subroot->inh_target_child_rels); + continue; + } + + /* + * Since we added the child rel directly into the join tree, we must + * modify it to be a "base" rel instead of an "other" rel, which the + * join planning code expects the relations being joined to be. + * + * NB: Do we need to change the child EC members to be marked + * as non-child somehow? + */ + childrel->reloptkind = RELOPT_BASEREL; + + Assert(subroot->join_rel_list == NIL); + Assert(subroot->join_rel_hash == NULL); + + /* Perform join planning and save the resulting RelOptInfo. */ + childrel = make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * Remember this child target rel. inheritance_planner will perform + * the remaining steps of planning for each child relation separately. + * Specifically, it will call grouping_planner on every + * RelOptInfo contained in the inh_target_child_rels list, each of + * which represents the source of tuples to be modified for a given + * target child rel. + */ + root->inh_target_child_rels = + lappend(root->inh_target_child_rels, childrel); +#ifdef USE_ASSERT_CHECKING + /* + * The following implements essentially the same Assert as in + * make_one_rel, our caller. + */ + all_baserels = bms_copy(root->all_baserels); + all_baserels = bms_del_member(all_baserels, + root->parse->resultRelation); + all_baserels = bms_add_member(all_baserels, + subroot->parse->resultRelation); + Assert(bms_equal(childrel->relids, all_baserels)); +#endif + } +} + +/* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. * diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index da7a92081a..28c4b53fea 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -1998,12 +1998,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) /* * During setrefs.c, we'll need the grouping_map to fix up the cols lists * in GroupingFunc nodes. Save it for setrefs.c to use. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be grouping in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->grouping_map == NULL); root->grouping_map = grouping_map; @@ -2160,12 +2155,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) * with InitPlan output params. (We can't just do that locally in the * MinMaxAgg node, because path nodes above here may have Agg references * as well.) Save the mmaggregates list to tell setrefs.c to do that. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be aggregates in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->minmax_aggs == NIL); root->minmax_aggs = best_path->mmaggregates; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index c729a99f8b..14e59188cd 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -37,6 +37,7 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "nodes/relation.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" @@ -126,7 +127,7 @@ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static void inheritance_planner(PlannerInfo *root); static void grouping_planner(PlannerInfo *root, bool inheritance_update, - double tuple_fraction); + RelOptInfo *planned_rel, double tuple_fraction); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, int *tleref_to_colnum_map); @@ -630,7 +631,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->grouping_map = NULL; root->minmax_aggs = NIL; root->qual_security_level = 0; - root->inhTargetKind = INHKIND_NONE; root->hasRecursion = hasRecursion; if (hasRecursion) root->wt_param_id = SS_assign_special_param(root); @@ -970,7 +970,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh) inheritance_planner(root); else - grouping_planner(root, false, tuple_fraction); + grouping_planner(root, false, NULL, tuple_fraction); /* * Capture the set of outer-level param IDs we have access to, for use in @@ -1146,12 +1146,19 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr) * inheritance set. * * We have to handle this case differently from cases where a source relation - * is an inheritance set. Source inheritance is expanded at the bottom of the - * plan tree (see allpaths.c), but target inheritance has to be expanded at - * the top. The reason is that for UPDATE, each target relation needs a - * different targetlist matching its own column set. Fortunately, - * the UPDATE/DELETE target can never be the nullable side of an outer join, - * so it's OK to generate the plan this way. + * is an inheritance set. That's mainly because for UPDATE, each target + * relation needs a different targetlist matching its own column set. So, we + * must modify the source scan/join path for each target relation such that it + * produces the desired target list. + * + * The source scan/join paths for individual target relations are still + * created in allpaths.c by first expanding the inheritance in the usual way + * by set_append_rel_size, followed by join planning for each target relation + * separately in make_one_rel. Finally, we apply grouping_planner here to each + * child scan/join path so that it produces the desired targetlist. + * + * Fortunately, the UPDATE/DELETE target can never be the nullable side of an + * outer join, so it's OK to generate the plan this way. * * Returns nothing; the useful output is in the Paths we attach to * the (UPPERREL_FINAL, NULL) upperrel stored in *root. @@ -1164,14 +1171,8 @@ inheritance_planner(PlannerInfo *root) { Query *parse = root->parse; int top_parentRTindex = parse->resultRelation; - Bitmapset *subqueryRTindexes; - Bitmapset *modifiableARIindexes; int nominalRelation = -1; Index rootRelation = 0; - List *final_rtable = NIL; - int save_rel_array_size = 0; - RelOptInfo **save_rel_array = NULL; - AppendRelInfo **save_append_rel_array = NULL; List *subpaths = NIL; List *subroots = NIL; List *resultRelations = NIL; @@ -1179,70 +1180,59 @@ inheritance_planner(PlannerInfo *root) List *returningLists = NIL; List *rowMarks; RelOptInfo *final_rel; - ListCell *lc; - Index rti; + ListCell *lc1, + *lc2; RangeTblEntry *parent_rte; - PlannerInfo *parent_root; - Query *parent_parse; - Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); - PlannerInfo **parent_roots = NULL; + List *tlist; + standard_qp_extra qp_extra; + RelOptInfo *planned_rel; + /* Inheritance is never used for insert. */ Assert(parse->commandType != CMD_INSERT); + parent_rte = planner_rt_fetch(top_parentRTindex, root); /* - * We generate a modified instance of the original Query for each target - * relation, plan that, and put all the plans into a list that will be - * controlled by a single ModifyTable node. All the instances share the - * same rangetable, but each instance must have its own set of subquery - * RTEs within the finished rangetable because (1) they are likely to get - * scribbled on during planning, and (2) it's not inconceivable that - * subqueries could get planned differently in different cases. We need - * not create duplicate copies of other RTE kinds, in particular not the - * target relations, because they don't have either of those issues. Not - * having to duplicate the target relations is important because doing so - * (1) would result in a rangetable of length O(N^2) for N targets, with - * at least O(N^3) work expended here; and (2) would greatly complicate - * management of the rowMarks list. + * Generate the access paths for all relations mentioned in the query, + * including the target inheritance set. When doing the join planning, + * references in the join tree to the original target relation that's the + * root parent of the inheritance tree is replaced by each of its + * inheritance children and the resulting joinrel RelOptInfo's are + * added to root->inh_target_child_rels. * - * To begin with, generate a bitmapset of the relids of the subquery RTEs. + * Final planning steps (grouping_planner) are applied to the best path + * of each of those child joinrels using a modified instance of the + * original query for a given child target rel. All the paths so generated + * are put into a list that will be controlled by a single ModifyTable + * node. All the instances share the same rangetable. */ - subqueryRTindexes = NULL; - rti = 1; - foreach(lc, parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - if (rte->rtekind == RTE_SUBQUERY) - subqueryRTindexes = bms_add_member(subqueryRTindexes, rti); - rti++; - } /* - * Next, we want to identify which AppendRelInfo items contain references - * to any of the aforesaid subquery RTEs. These items will need to be - * copied and modified to adjust their subquery references; whereas the - * other ones need not be touched. It's worth being tense over this - * because we can usually avoid processing most of the AppendRelInfo - * items, thereby saving O(N^2) space and time when the target is a large - * inheritance tree. We can identify AppendRelInfo items by their - * child_relid, since that should be unique within the list. + * Save the unexpanded version of the query's targetlist to be used below + * for passing to grouping_planner for each child target relation. */ - modifiableARIindexes = NULL; - if (subqueryRTindexes != NULL) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); + root->unexpanded_tlist = list_copy(root->parse->targetList); - if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) || - bms_is_member(appinfo->child_relid, subqueryRTindexes) || - bms_overlap(pull_varnos((Node *) appinfo->translated_vars), - subqueryRTindexes)) - modifiableARIindexes = bms_add_member(modifiableARIindexes, - appinfo->child_relid); - } + /* Do the scan/join planning. */ + tlist = preprocess_targetlist(root); + root->processed_tlist = tlist; + qp_extra.tlist = tlist; + qp_extra.activeWindows = qp_extra.groupClause = NIL; + planned_rel = query_planner(root, tlist, standard_qp_callback, &qp_extra); + + /* + * If it turned out during query planning that all the children are dummy + * (pruned or excluded by constraints), no need to do the steps below. + * Let grouping_planner finish up the final path. + */ + if (IS_DUMMY_REL(planned_rel)) + { + grouping_planner(root, false, planned_rel, 0.0); + return; } + Assert(planned_rel->relid == top_parentRTindex); + Assert(planned_rel->reloptkind == RELOPT_BASEREL); + /* * If the parent RTE is a partitioned table, we should use that as the * nominal target relation, because the RTEs added for partitioned tables @@ -1250,7 +1240,6 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1258,59 +1247,23 @@ inheritance_planner(PlannerInfo *root) } /* - * The PlannerInfo for each child is obtained by translating the relevant - * members of the PlannerInfo for its immediate parent, which we find - * using the parent_relid in its AppendRelInfo. We save the PlannerInfo - * for each parent in an array indexed by relid for fast retrieval. Since - * the maximum number of parents is limited by the number of RTEs in the - * query, we use that number to allocate the array. An extra entry is - * needed since relids start from 1. + * Get on with finalizing the path for each child target relation by + * calling grouping_planner on its joinrel. Note that we're restoring + * the query's targetlist to the original one for grouping_planner's + * targetlist expansion steps to perform the expansion with individual + * child descriptors. */ - parent_roots = (PlannerInfo **) palloc0((list_length(parse->rtable) + 1) * - sizeof(PlannerInfo *)); - parent_roots[top_parentRTindex] = root; - - /* - * And now we can get on with generating a plan for each child table. - */ - foreach(lc, root->append_rel_list) + forboth(lc1, root->inh_target_child_roots, + lc2, root->inh_target_child_rels) { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - PlannerInfo *subroot; + PlannerInfo *subroot = lfirst(lc1); + RelOptInfo *childrel = lfirst(lc2); + AppendRelInfo *appinfo; RangeTblEntry *child_rte; RelOptInfo *sub_final_rel; Path *subpath; - /* append_rel_list contains all append rels; ignore others */ - if (!bms_is_member(appinfo->parent_relid, parent_relids)) - continue; - - /* - * expand_inherited_rtentry() always processes a parent before any of - * that parent's children, so the parent_root for this relation should - * already be available. - */ - parent_root = parent_roots[appinfo->parent_relid]; - Assert(parent_root != NULL); - parent_parse = parent_root->parse; - - /* - * We need a working copy of the PlannerInfo so that we can control - * propagation of information back to the main copy. - */ - subroot = makeNode(PlannerInfo); - memcpy(subroot, parent_root, sizeof(PlannerInfo)); - - /* - * Generate modified query with this rel as target. We first apply - * adjust_appendrel_attrs, which copies the Query and changes - * references to the parent RTE to refer to the current child RTE, - * then fool around with subquery RTEs. - */ - subroot->parse = (Query *) - adjust_appendrel_attrs(parent_root, - (Node *) parent_parse, - 1, &appinfo); + appinfo = root->append_rel_array[subroot->parse->resultRelation]; /* * If there are securityQuals attached to the parent, move them to the @@ -1322,31 +1275,12 @@ inheritance_planner(PlannerInfo *root) parent_rte->securityQuals = NIL; /* - * Mark whether we're planning a query to a partitioned table or an - * inheritance parent. + * Ignore a partitioned child. Instead, the paths of its children will + * be added to subpaths. */ - subroot->inhTargetKind = - (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED; - - /* - * If this child is further partitioned, remember it as a parent. - * Since a partitioned table does not have any data, we don't need to - * create a plan for it, and we can stop processing it here. We do, - * however, need to remember its modified PlannerInfo for use when - * processing its children, since we'll update their varnos based on - * the delta from immediate parent to child, not from top to child. - * - * Note: a very non-obvious point is that we have not yet added - * duplicate subquery RTEs to the subroot's rtable. We mustn't, - * because then its children would have two sets of duplicates, - * confusing matters. - */ - if (child_rte->inh) + if (childrel->part_scheme) { - Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE); - parent_relids = bms_add_member(parent_relids, appinfo->child_relid); - parent_roots[appinfo->child_relid] = subroot; - + Assert(child_rte->inh); continue; } @@ -1374,111 +1308,8 @@ inheritance_planner(PlannerInfo *root) if (nominalRelation < 0) nominalRelation = appinfo->child_relid; - /* - * The rowMarks list might contain references to subquery RTEs, so - * make a copy that we can apply ChangeVarNodes to. (Fortunately, the - * executor doesn't need to see the modified copies --- we can just - * pass it the original rowMarks list.) - */ - subroot->rowMarks = copyObject(parent_root->rowMarks); - - /* - * The append_rel_list likewise might contain references to subquery - * RTEs (if any subqueries were flattenable UNION ALLs). So prepare - * to apply ChangeVarNodes to that, too. As explained above, we only - * want to copy items that actually contain such references; the rest - * can just get linked into the subroot's append_rel_list. - * - * If we know there are no such references, we can just use the outer - * append_rel_list unmodified. - */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - subroot->append_rel_list = NIL; - foreach(lc2, parent_root->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, modifiableARIindexes)) - appinfo2 = copyObject(appinfo2); - - subroot->append_rel_list = lappend(subroot->append_rel_list, - appinfo2); - } - } - - /* - * Add placeholders to the child Query's rangetable list to fill the - * RT indexes already reserved for subqueries in previous children. - * These won't be referenced, so there's no need to make them very - * valid-looking. - */ - while (list_length(subroot->parse->rtable) < list_length(final_rtable)) - subroot->parse->rtable = lappend(subroot->parse->rtable, - makeNode(RangeTblEntry)); - - /* - * If this isn't the first child Query, generate duplicates of all - * subquery RTEs, and adjust Var numbering to reference the - * duplicates. To simplify the loop logic, we scan the original rtable - * not the copy just made by adjust_appendrel_attrs; that should be OK - * since subquery RTEs couldn't contain any references to the target - * rel. - */ - if (final_rtable != NIL && subqueryRTindexes != NULL) - { - ListCell *lr; - - rti = 1; - foreach(lr, parent_parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lr); - - if (bms_is_member(rti, subqueryRTindexes)) - { - Index newrti; - - /* - * The RTE can't contain any references to its own RT - * index, except in its securityQuals, so we can save a - * few cycles by applying ChangeVarNodes to the rest of - * the rangetable before we append the RTE to it. - */ - newrti = list_length(subroot->parse->rtable) + 1; - ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); - ChangeVarNodes((Node *) subroot->rowMarks, rti, newrti, 0); - /* Skip processing unchanging parts of append_rel_list */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - foreach(lc2, subroot->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, - modifiableARIindexes)) - ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); - } - } - rte = copyObject(rte); - ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); - subroot->parse->rtable = lappend(subroot->parse->rtable, - rte); - } - rti++; - } - } - - /* There shouldn't be any OJ info to translate, as yet */ - Assert(subroot->join_info_list == NIL); - /* and we haven't created PlaceHolderInfos, either */ - Assert(subroot->placeholder_list == NIL); - - /* Generate Path(s) for accessing this result relation */ - grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ ); + /* Apply the top-level targetlist to childrel's path. */ + grouping_planner(subroot, true, childrel, 0.0); /* * Select cheapest path in case there's more than one. We always run @@ -1490,45 +1321,10 @@ inheritance_planner(PlannerInfo *root) subpath = sub_final_rel->cheapest_total_path; /* - * If this child rel was excluded by constraint exclusion, exclude it - * from the result plan. + * child rel cannot be empty, or inheritance_make_rel_from_joinlist + * wouldn't have put it in the list to begin with. */ - if (IS_DUMMY_PATH(subpath)) - continue; - - /* - * If this is the first non-excluded child, its post-planning rtable - * becomes the initial contents of final_rtable; otherwise, append - * just its modified subquery RTEs to final_rtable. - */ - if (final_rtable == NIL) - final_rtable = subroot->parse->rtable; - else - final_rtable = list_concat(final_rtable, - list_copy_tail(subroot->parse->rtable, - list_length(final_rtable))); - - /* - * We need to collect all the RelOptInfos from all child plans into - * the main PlannerInfo, since setrefs.c will need them. We use the - * last child's simple_rel_array (previous ones are too short), so we - * have to propagate forward the RelOptInfos that were already built - * in previous children. - */ - Assert(subroot->simple_rel_array_size >= save_rel_array_size); - for (rti = 1; rti < save_rel_array_size; rti++) - { - RelOptInfo *brel = save_rel_array[rti]; - - if (brel) - subroot->simple_rel_array[rti] = brel; - } - save_rel_array_size = subroot->simple_rel_array_size; - save_rel_array = subroot->simple_rel_array; - save_append_rel_array = subroot->append_rel_array; - - /* Make sure any initplans from this rel get into the outer list */ - root->init_plans = subroot->init_plans; + Assert(!IS_DUMMY_PATH(subpath)); /* Build list of sub-paths */ subpaths = lappend(subpaths, subpath); @@ -1560,36 +1356,6 @@ inheritance_planner(PlannerInfo *root) */ /* - * If we managed to exclude every child rel, return a dummy plan; it - * doesn't even need a ModifyTable node. - */ - if (subpaths == NIL) - { - set_dummy_rel_pathlist(final_rel); - return; - } - - /* - * Put back the final adjusted rtable into the master copy of the Query. - * (We mustn't do this if we found no non-excluded children.) - */ - parse->rtable = final_rtable; - root->simple_rel_array_size = save_rel_array_size; - root->simple_rel_array = save_rel_array; - root->append_rel_array = save_append_rel_array; - - /* Must reconstruct master's simple_rte_array, too */ - root->simple_rte_array = (RangeTblEntry **) - palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *)); - rti = 1; - foreach(lc, final_rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - root->simple_rte_array[rti++] = rte; - } - - /* * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will * have dealt with fetching non-locked marked rows, else we need to have * ModifyTable do that. @@ -1629,6 +1395,12 @@ inheritance_planner(PlannerInfo *root) * (inheritance_planner will create a single ModifyTable node covering all the * target tables.) * + * If non-NULL, planned_rel is a RelOptInfo containing paths for the query's + * top-level joinrel, which the caller produced by itself. In that case, this + * function only needs to adjust the targetlist of its cheapest_total_path. + * The only caller that may pass such a RelOptInfo currently is + * inheritance_planner. + * * tuple_fraction is the fraction of tuples we expect will be retrieved. * tuple_fraction is interpreted as follows: * 0: expect all tuples to be retrieved (normal case) @@ -1647,6 +1419,7 @@ inheritance_planner(PlannerInfo *root) */ static void grouping_planner(PlannerInfo *root, bool inheritance_update, + RelOptInfo *planned_rel, double tuple_fraction) { Query *parse = root->parse; @@ -1659,7 +1432,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, List *final_targets; List *final_targets_contain_srfs; bool final_target_parallel_safe; - RelOptInfo *current_rel; + RelOptInfo *current_rel = planned_rel; RelOptInfo *final_rel; ListCell *lc; @@ -1699,6 +1472,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * special work for recursive unions is the responsibility of * plan_set_operations. */ + Assert(current_rel == NULL); current_rel = plan_set_operations(root); /* @@ -1788,17 +1562,29 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, parse->groupClause = preprocess_groupclause(root, NIL); } - /* Preprocess targetlist */ - tlist = preprocess_targetlist(root); - /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation so - * that we can transfer its decoration (resnames etc) to the topmost - * tlist of the finished Plan. + * Preprocess targetlist, if needed. If the caller has already done + * query planning, root->processed_tlist already contain the desired + * targetlist. */ - root->processed_tlist = tlist; + if (planned_rel == NULL) + { + tlist = preprocess_targetlist(root); + + /* + * We are now done hacking up the query's targetlist. Most of the + * remaining planning work will be done with the PathTarget + * representation of tlists, but save aside the full representation + * so that we can transfer its decoration (resnames etc) to the + * topmost tlist of the finished Plan. + */ + root->processed_tlist = tlist; + } + else + { + Assert(root->processed_tlist != NIL); + tlist = root->processed_tlist; + } /* * Collect statistics about aggregates for estimating costs, and mark @@ -1878,8 +1664,9 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * We also generate (in standard_qp_callback) pathkey representations * of the query's sort clause, distinct clause, etc. */ - current_rel = query_planner(root, tlist, - standard_qp_callback, &qp_extra); + if (current_rel == NULL) + current_rel = query_planner(root, tlist, + standard_qp_callback, &qp_extra); /* * Convert the query's result tlist into PathTarget format. diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index cd6e11904e..78baec00dc 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -917,7 +917,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->grouping_map = NULL; subroot->minmax_aggs = NIL; subroot->qual_security_level = 0; - subroot->inhTargetKind = INHKIND_NONE; subroot->hasRecursion = false; subroot->wt_param_id = -1; subroot->non_recursive_path = NULL; diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index d5720518a8..ef1f978889 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -2253,8 +2253,39 @@ adjust_appendrel_attrs_mutator(Node *node, context->appinfos); return (Node *) phv; } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 0c88c90de4..ed0953f9e1 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1264,36 +1264,6 @@ get_relation_constraints(PlannerInfo *root, } } - /* - * Append partition predicates, if any. - * - * For selects, partition pruning uses the parent table's partition bound - * descriptor, instead of constraint exclusion which is driven by the - * individual partition's partition constraint. - */ - if (enable_partition_pruning && root->parse->commandType != CMD_SELECT) - { - List *pcqual = RelationGetPartitionQual(relation); - - if (pcqual) - { - /* - * Run the partition quals through const-simplification similar to - * check constraints. We skip canonicalize_qual, though, because - * partition quals should be in canonical form already; also, - * since the qual is in implicit-AND format, we'd have to - * explicitly convert it to explicit-AND format and back again. - */ - pcqual = (List *) eval_const_expressions(root, (Node *) pcqual); - - /* Fix Vars to have the desired varno */ - if (varno != 1) - ChangeVarNodes((Node *) pcqual, 1, varno, 0); - - result = list_concat(result, pcqual); - } - } - heap_close(relation, NoLock); return result; @@ -1420,31 +1390,15 @@ relation_excluded_by_constraints(PlannerInfo *root, switch (constraint_exclusion) { case CONSTRAINT_EXCLUSION_OFF: - - /* - * Don't prune if feature turned off -- except if the relation is - * a partition. While partprune.c-style partition pruning is not - * yet in use for all cases (update/delete is not handled), it - * would be a UI horror to use different user-visible controls - * depending on such a volatile implementation detail. Therefore, - * for partitioned tables we use enable_partition_pruning to - * control this behavior. - */ - if (root->inhTargetKind == INHKIND_PARTITIONED) - break; return false; case CONSTRAINT_EXCLUSION_PARTITION: /* * When constraint_exclusion is set to 'partition' we only handle - * OTHER_MEMBER_RELs, or BASERELs in cases where the result target - * is an inheritance parent or a partitioned table. + * OTHER_MEMBER_RELs. */ - if ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) && - !(rel->reloptkind == RELOPT_BASEREL && - root->inhTargetKind != INHKIND_NONE && - rel->relid == root->parse->resultRelation)) + if (rel->reloptkind != RELOPT_OTHER_MEMBER_REL) return false; break; diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 6fd24203dd..2ad3dc4711 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -319,9 +319,6 @@ typedef struct PlannerInfo Index qual_security_level; /* minimum security_level for quals */ /* Note: qual_security_level is zero if there are no securityQuals */ - InheritanceKind inhTargetKind; /* indicates if the target relation is an - * inheritance child or partition or a - * partitioned table */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */ bool hasDeletedRTEs; /* true if any RTE was deleted from jointree */ @@ -343,6 +340,34 @@ typedef struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* + * The following fields are set during query planning portion of an + * inherited UPDATE/DEELETE operation. + */ + + /* + * This stores the original version of the query's targetlist that's + * not modified by the planner. + */ + List *unexpanded_tlist; + + /* + * List containing a PlannerInfo corresponding to each child target rel. + * Content of each PlannerInfo is same as the parent PlannerInfo, except + * for the parse tree which is a translated copy of the parent's parse + * tree. + */ + List *inh_target_child_roots; + + /* + * RelOptInfos corresponding to each child target rel. For leaf children, + * it's the RelOptInfo representing the output of make_rel_from_joinlist() + * called with the parent rel in the original join tree replaced by a + * given leaf child. For non-leaf children, it's the baserel RelOptInfo + * itself, left as a placeholder. + */ + List *inh_target_child_rels; } PlannerInfo; diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index 3ba3aaf2d8..a539280851 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1781,7 +1781,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_1 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_1 -> Nested Loop Semi Join @@ -1789,7 +1789,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_2 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_2 (28 rows) -- 2.11.0
v6-0002-Store-inheritance-root-parent-index-in-otherrel-s.patch (text/plain, 2.5K) download From 26c8691391ee149c317c03f2a99a671d068fd926 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 26 Oct 2018 16:45:59 +0900 Subject: [PATCH v6 2/6] Store inheritance root parent index in otherrel's RelOptInfo Although it's set by build_simple_rel, it's not being used by any code yet. --- src/backend/nodes/outfuncs.c | 1 + src/backend/optimizer/util/relnode.c | 14 ++++++++++++++ src/include/nodes/relation.h | 4 ++++ 3 files changed, 19 insertions(+) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 08657fcfd9..1d7c420e75 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2370,6 +2370,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_BOOL_FIELD(consider_partitionwise_join); WRITE_BITMAPSET_FIELD(top_parent_relids); WRITE_NODE_FIELD(partitioned_child_rels); + WRITE_UINT_FIELD(inh_root_parent); } static void diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 39f5729b91..29ba19349f 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -215,9 +215,23 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->top_parent_relids = parent->top_parent_relids; else rel->top_parent_relids = bms_copy(parent->relids); + + /* + * For inheritance child relations, we also set inh_root_parent. + * Note that 'parent' might itself be a child (a sub-partitioned + * partition), in which case we simply use its value of + * inh_root_parent. + */ + if (parent->rtekind == RTE_RELATION) + rel->inh_root_parent = parent->inh_root_parent > 0 ? + parent->inh_root_parent : + parent->relid; } else + { rel->top_parent_relids = NULL; + rel->inh_root_parent = 0; + } /* Check type of rtable entry */ switch (rte->rtekind) diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 2ad3dc4711..94f14019bd 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -728,6 +728,10 @@ typedef struct RelOptInfo List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ + + Index inh_root_parent; /* For otherrels, this is the RT index of + * inheritance table mentioned in the query + * from which this relation originated */ } RelOptInfo; /* -- 2.11.0
v6-0003-Lazy-creation-of-RTEs-for-inheritance-children.patch (text/plain, 85.4K) download From 540557dba91fc10087b05f356a2f7e2caf376897 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 18:14:13 +0900 Subject: [PATCH v6 3/6] Lazy creation of RTEs for inheritance children Currently, expand_inherited_tables called from subquery_planner opens all inheritance child tables and adds them to the query's range table and PlannerInfo in the form of AppendRelInfo's initially and later RelOptInfo's. For partitioning, that's pretty wasteful, because it's possible to determine only the partitions that will need to be scanned using partition pruning, which doesn't require opening the partitions themselves. However, the point at which the above processing happens is too early to perform partition pruning. This commit rearranges things within the planner so that inheritance child relations can be added to the Query and PlannerInfo during make_one_rel, just before set_base_rel_sizes is called. Although the late initialization approach only benefits the partitioning case, it seems undesirable to do it only for partitioned tables, because it means adding code to handle partitions specially at various places within the planner. So, *all* inheritance parent tables are expanded in make_one_rel. All unpruned partitions are added to Query in the form of their RangeTblEntry's being added to the range table and to PlannerInfo in the form of an AppendRelInfo and a RelOptInfo for each. For partitioning, although we don't create a RangeTblEntry and RelOptInfo for pruned partitions at make_one_rel time, partitionwise join code relies on the fact that even though partitions may have been pruned, they'd still own a RelOptInfo to handle the outer join case where the pruned partition appears on the nullable side of join. Partitionwise join code deals with that by allocating dummy RelOptInfos for pruned partitions that are based mostly on their parent's properties. There are some regression test diffs: 1. Caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child, as seen in the partition_aggregate.out test output. 2. Those in postgres_fdw.out are caused by the fact that junk columns required for row marking are added to reltarget->exprs later than user columns, because the row marking junk columns arent added until after the inheritance is expanded which as of this commit is later than it used to be. --- contrib/postgres_fdw/expected/postgres_fdw.out | 32 +- src/backend/optimizer/path/allpaths.c | 222 ++---- src/backend/optimizer/path/joinrels.c | 62 ++ src/backend/optimizer/plan/initsplan.c | 58 -- src/backend/optimizer/plan/planner.c | 60 +- src/backend/optimizer/plan/setrefs.c | 6 + src/backend/optimizer/prep/preptlist.c | 15 +- src/backend/optimizer/prep/prepunion.c | 852 +++++++++++++++------- src/backend/optimizer/util/Makefile | 2 +- src/backend/optimizer/util/plancat.c | 40 +- src/backend/optimizer/util/relnode.c | 80 +- src/backend/partitioning/partprune.c | 43 +- src/include/nodes/relation.h | 8 + src/include/optimizer/pathnode.h | 1 + src/include/optimizer/plancat.h | 2 +- src/include/optimizer/prep.h | 9 +- src/include/partitioning/partprune.h | 2 +- src/test/regress/expected/partition_aggregate.out | 4 +- 18 files changed, 882 insertions(+), 616 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 21a2ef5ad3..f546260af8 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7085,15 +7085,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7123,15 +7123,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7160,15 +7160,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid @@ -7178,15 +7178,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (39 rows) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 7787592870..a4dd34f2cd 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -44,7 +44,6 @@ #include "optimizer/var.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -180,6 +179,16 @@ make_one_rel(PlannerInfo *root, List *joinlist) set_base_rel_consider_startup(root); /* + * Expand RT entries that represent inherited or partitioned tables. + * This will perform partition pruning on partitioned tables in the + * original range table and also recursively on any child partitioned + * tables that were added by the expansion of the original parent(s). + * As new entries are added to the range table, various arrays in the + * PlannerInfo will be expanded accordingly. + */ + expand_inherited_tables(root); + + /* * Compute size estimates and consider_parallel flags for each base rel. */ set_base_rel_sizes(root); @@ -404,7 +413,11 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, */ set_dummy_rel_pathlist(rel); } - else if (rte->inh) + /* + * expand_inherited_tables may have proved that the relation is empty, so + * check if it's so. + */ + else if (rte->inh && !IS_DUMMY_REL(rel)) { /* It's an "append relation", process accordingly */ set_append_rel_size(root, rel, rti, rte); @@ -957,8 +970,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -966,32 +977,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Assert(IS_SIMPLE_REL(rel)); /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, we will bubble up - * the indexes of partitioned relations that appear down in the tree, so - * that when we've created Paths for all the children, the root - * partitioned table's list will contain all such indexes. - */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - rel->partitioned_child_rels = list_make1_int(rti); - - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - - /* * If this is a partitioned baserel, set the consider_partitionwise_join * flag; currently, we only consider partitionwise joins with the baserel * if its targetlist doesn't contain a whole-row Var. @@ -1028,12 +1013,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; ListCell *parentvars; ListCell *childvars; - ListCell *lc; PlannerInfo *subroot = root; /* append_rel_list contains all append rels; ignore others */ @@ -1042,18 +1023,34 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo is created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); /* - * Copy/Modify targetlist. Even if this child is deemed empty, we need - * its targetlist in case it falls on nullable side in a child-join - * because of partitionwise join. + * Child relation may have be marked dummy if build_append_child_rel + * found self-contradictory quals. + */ + if (IS_DUMMY_REL(childrel)) + continue; + + if (relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* + * This child need not be scanned, so we can omit it from the + * appendrel. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Copy/Modify targetlist. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -1082,144 +1079,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, add_child_rel_equivalences(root, appinfo, rel, childrel); childrel->has_eclass_joins = rel->has_eclass_joins; - /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* - * This child need not be scanned, so we can omit it from the - * appendrel. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - /* CE failed, so finish copying/modifying join quals. */ childrel->joininfo = (List *) adjust_appendrel_attrs(root, @@ -1284,8 +1143,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, /* * If the childrel itself was a partitioned table, its children - * would've been added into subroot. Copy relevant fields - * into the parent root. + * would've been added into subroot. */ if (subroot->inh_target_child_roots != NIL) root->inh_target_child_roots = @@ -1442,9 +1300,10 @@ adjust_inherit_target_child(PlannerInfo *root, RelOptInfo *childrel, /* * Apply planner's expansion of targetlist, such as adding various junk * column, filling placeholder entries for dropped columns, etc., all of - * which occurs with the child's TupleDesc. + * which occurs with the child's TupleDesc. Since inheritance has already + * been expanded (we wouldn't be here otherwise), pass true. */ - tlist = preprocess_targetlist(subroot); + tlist = preprocess_targetlist(subroot, true); subroot->processed_tlist = tlist; build_base_rel_tlists(subroot, tlist); @@ -2826,6 +2685,7 @@ inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) Assert(subroot->parse->resultRelation > 0); childrel = find_base_rel(root, subroot->parse->resultRelation); + Assert(childrel != NULL); appinfo = root->append_rel_array[subroot->parse->resultRelation]; if (appinfo->parent_relid != root->parse->resultRelation) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index d3d21fed5d..da0831de4e 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -20,6 +20,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/prep.h" +#include "optimizer/tlist.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -46,6 +47,9 @@ static void try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, List *parent_restrictlist); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + int partidx); /* @@ -1376,6 +1380,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts); + if (child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts); + /* We should never try to join two overlapping sets of rels. */ Assert(!bms_overlap(child_rel1->relids, child_rel2->relids)); child_joinrelids = bms_union(child_rel1->relids, child_rel2->relids); @@ -1582,3 +1591,56 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx) +{ + RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + /* Pass parent's info as for both the parent rel and child rel. */ + AppendRelInfo *appinfo = make_append_rel_info(parent, parentrte, + parent->tupdesc, + parentrte->relid, + parent->reltype, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 01335db511..7636aa82c4 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -616,64 +616,6 @@ create_lateral_join_info(PlannerInfo *root) bms_add_member(brel2->lateral_referencers, rti); } } - - /* - * Lastly, propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - */ - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - RangeTblEntry *brte = root->simple_rte_array[rti]; - - /* - * Skip empty slots. Also skip non-simple relations i.e. dead - * relations. - */ - if (brel == NULL || !IS_SIMPLE_REL(brel)) - continue; - - /* - * In the case of table inheritance, the parent RTE is directly linked - * to every child table via an AppendRelInfo. In the case of table - * partitioning, the inheritance hierarchy is expanded one level at a - * time rather than flattened. Therefore, an other member rel that is - * a partitioned table may have children of its own, and must - * therefore be marked with the appropriate lateral info so that those - * children eventually get marked also. - */ - Assert(brte); - if (brel->reloptkind == RELOPT_OTHER_MEMBER_REL && - (brte->rtekind != RTE_RELATION || - brte->relkind != RELKIND_PARTITIONED_TABLE)) - continue; - - if (brte->inh) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); - RelOptInfo *childrel; - - if (appinfo->parent_relid != rti) - continue; - childrel = root->simple_rel_array[appinfo->child_relid]; - Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - Assert(childrel->direct_lateral_relids == NULL); - childrel->direct_lateral_relids = brel->direct_lateral_relids; - Assert(childrel->lateral_relids == NULL); - childrel->lateral_relids = brel->lateral_relids; - Assert(childrel->lateral_referencers == NULL); - childrel->lateral_referencers = brel->lateral_referencers; - } - } - } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 14e59188cd..5801ea0854 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -23,6 +23,7 @@ #include "access/sysattr.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -699,27 +700,24 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } /* * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). + * pullup (so that all non-inherited RTEs are present). */ preprocess_rowmarks(root); /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain base relations not joins, so it's OK (and marginally more - * efficient) to do it after checking for join RTEs. We must do it after - * pulling up subqueries, else we'd fail to handle inherited tables in - * subqueries. - */ - expand_inherited_tables(root); - - /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -1212,8 +1210,11 @@ inheritance_planner(PlannerInfo *root) */ root->unexpanded_tlist = list_copy(root->parse->targetList); - /* Do the scan/join planning. */ - tlist = preprocess_targetlist(root); + /* + * Do the scan/join planning. We haven't expanded inheritance yet, so + * pass false. + */ + tlist = preprocess_targetlist(root, false); root->processed_tlist = tlist; qp_extra.tlist = tlist; qp_extra.activeWindows = qp_extra.groupClause = NIL; @@ -1222,9 +1223,10 @@ inheritance_planner(PlannerInfo *root) /* * If it turned out during query planning that all the children are dummy * (pruned or excluded by constraints), no need to do the steps below. - * Let grouping_planner finish up the final path. + * For regular inheritance case, it's possible that we only need to modify + * the parent table. Let grouping_planner finish up the final path. */ - if (IS_DUMMY_REL(planned_rel)) + if (!parent_rte->inh || IS_DUMMY_REL(planned_rel)) { grouping_planner(root, false, planned_rel, 0.0); return; @@ -1569,7 +1571,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, */ if (planned_rel == NULL) { - tlist = preprocess_targetlist(root); + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root, false); /* * We are now done hacking up the query's targetlist. Most of the @@ -1669,6 +1672,15 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, standard_qp_callback, &qp_extra); /* + * Query planning may have added some columns to the top-level tlist, + * which happens when there are row marks applied to inheritance + * parent relations (additional junk columns needed for applying row + * marks are added after expanding inheritance.) + */ + if (list_length(tlist) < list_length(root->processed_tlist)) + tlist = root->processed_tlist; + + /* * Convert the query's result tlist into PathTarget format. * * Note: it's desirable to not do this till after query_planner(), @@ -2355,7 +2367,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2380,7 +2392,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } @@ -6760,6 +6772,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -6861,6 +6877,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 6d6ef1c376..454870609a 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -326,6 +326,12 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) UPPERREL_FINAL, NULL))) add_rtes_to_flat_rtable(rel->subroot, true); } + /* + * A NULL rel also means an unplanned subquery rte, so apply + * flatten_unplanned_rtes. + */ + else + flatten_unplanned_rtes(glob, rte); } rti++; } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 8603feef2b..0747403acd 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -65,9 +65,13 @@ static List *expand_targetlist(List *tlist, int command_type, * * As a side effect, if there's an ON CONFLICT UPDATE clause, its targetlist * is also preprocessed (and updated in-place). + * + * inheritance_expanded specifies whether inheritance tables are expanded + * as of calling this function. Caller must specify it because that affects + * which row marking related junk columns get added to the targetlist. */ List * -preprocess_targetlist(PlannerInfo *root) +preprocess_targetlist(PlannerInfo *root, bool inheritance_expanded) { Query *parse = root->parse; int result_relation = parse->resultRelation; @@ -134,6 +138,15 @@ preprocess_targetlist(PlannerInfo *root) if (rc->rti != rc->prti) continue; + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which will change the parent row + * mark's allMarkTypes fields. + */ + if (rc->isParent && !inheritance_expanded) + continue; + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) { /* Need to fetch TID */ diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index ef1f978889..e4d4e7d6dd 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -39,15 +39,19 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" +#include "optimizer/var.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" +#include "partitioning/partprune.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/selfuncs.h" @@ -99,23 +103,26 @@ static List *generate_append_tlist(List *colTypes, List *colCollations, List *input_tlists, List *refnames_tlist); static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist); +static void expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti); static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); + Index rti, RelOptInfo *rel); static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); -static void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p); +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); +static RelOptInfo *build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, @@ -1472,33 +1479,134 @@ generate_setop_grouplist(SetOperationStmt *op, List *targetlist) void expand_inherited_tables(PlannerInfo *root) { - Index nrtes; + int orig_rtable_size; Index rti; - ListCell *rl; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; /* - * expand_inherited_rtentry may add RTEs to parse->rtable. The function is + * expand_append_rtentry may add RTEs to parse->rtable. The function is * expected to recursively handle any RTEs that it creates with inh=true. * So just scan as far as the original end of the rtable list. */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) + for (rti = 1; rti < orig_rtable_size; rti++) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + if (rte->inh) + expand_append_rtentry(root, brel, rte, rti); + } +} + +/* + * expand_append_rtentry + * This initializes RelOptInfos for an appendrel's child relations, if + * any + * + * 'rel' is the appendrel parent, whose range table entry ('rte') has been + * marked to require adding children. An appendrel parent could either + * be a subquery (if we flattened UNION ALL query) or a table that's known + * to have inheritance children. The latter consists of both regular + * inheritance parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as an appendrel child. The original RTE is + * considered to represent the whole inheritance set. + */ +static void +expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, + Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_append_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_inherited_rtentry(root, rte, rti, rel); } } /* * expand_inherited_rtentry - * Check whether a rangetable entry represents an inheritance set. - * If so, add entries for all the child tables to the query's - * rangetable, and build AppendRelInfo nodes for all the child tables - * and add them to root->append_rel_list. If not, clear the entry's - * "inh" flag to prevent later code from looking for AppendRelInfos. + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. * * Note that the original RTE is considered to represent the whole * inheritance set. The first of the generated RTEs is an RTE for the same @@ -1509,45 +1617,22 @@ expand_inherited_tables(PlannerInfo *root) * regular inheritance, a parent RTE must always have at least two associated * AppendRelInfos: one corresponding to the parent table as a simple member of * inheritance set and one or more corresponding to the actual children. - * Since a partitioned table is not scanned, it might have only one associated - * AppendRelInfo. */ static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, + RelOptInfo *rel) { Oid parentOID; PlanRowMark *oldrc; - Relation oldrelation; - LOCKMODE lockmode; + LOCKMODE lockmode = rte->rellockmode; List *inhOIDs; ListCell *l; + int num_children; + int num_children_added = 0; - /* Does RT entry allow inheritance? */ - if (!rte->inh) - return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } - /* Fast path for common case of childless table */ + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); parentOID = rte->relid; - if (!has_subclass(parentOID)) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * The rewriter should already have obtained an appropriate lock on each - * relation named in the query. However, for each child relation we add - * to the query, we must obtain an appropriate lock, because this will be - * the first use of those relations in the parse/rewrite/plan pipeline. - * Child rels should use the same lockmode as their parent. - */ - lockmode = rte->rellockmode; /* Scan for all members of inheritance set, acquire needed locks */ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); @@ -1557,7 +1642,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) * case. This could happen despite above has_subclass() check, if table * once had a child but no longer does. */ - if (list_length(inhOIDs) < 2) + num_children = list_length(inhOIDs); + if (num_children < 2) { /* Clear flag before returning */ rte->inh = false; @@ -1565,219 +1651,220 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } /* - * If parent relation is selected FOR UPDATE/SHARE, we need to mark its - * PlanRowMark as isParent = true, and generate a new PlanRowMark for each - * child. + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. */ oldrc = get_plan_rowmark(root->rowMarks, rti); - if (oldrc) - oldrc->isParent = true; + Assert(oldrc == NULL || oldrc->isParent); /* - * Must open the parent relation to examine its tupdesc. We need not lock - * it; we assume the rewriter already did. + * Must expand PlannerInfo arrays by num_children before we can add + * children. */ - oldrelation = heap_open(parentOID, NoLock); + expand_planner_arrays(root, num_children); - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) + foreach(l, inhOIDs) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); - - /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. - */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); - } - else - { - List *appinfos = NIL; + Oid childOID = lfirst_oid(l); + Relation newrelation; RangeTblEntry *childrte; Index childRTindex; + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. */ - foreach(l, inhOIDs) + if (RELATION_IS_OTHER_TEMP(newrelation)) { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = heap_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - heap_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - heap_close(newrelation, NoLock); + heap_close(newrelation, lockmode); + continue; } - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); + (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation, &childrte, + &childRTindex); + Assert(childrte != NULL); + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + Assert(childRTindex > 0); + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; } - heap_close(oldrelation, NoLock); + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } } /* * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo * - * Note that RelationGetPartitionDispatchInfo will expand partitions in the - * same order as this code. + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * in order to avoid partitions being locked in a different order than other + * places in the backend that may lock partitions. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) + Index parentRTindex, RelOptInfo *parentrel) { + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; - check_stack_depth(); - - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + expand_planner_arrays(root, bms_num_members(partindexes)); /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. */ - if (partdesc->nparts == 0) - { - parentrte->inh = false; - return; - } + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); - for (i = 0; i < partdesc->nparts; i++) + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) { - Oid childOID = partdesc->oids[i]; - Relation childrel; + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + RangeTblEntry *childrte; + Index childRTindex; - /* Open rel; we already have required locks */ - childrel = heap_open(childOID, NoLock); + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation, + &childrte, &childRTindex); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + + /* If the child is partitioned itself, expand it too. */ + if (childrel->part_scheme) + { + Assert(childrte->inh); expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); + childrel); + } + } - /* Close child relation, but keep locks */ - heap_close(childrel, NoLock); + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); } } /* - * expand_single_inheritance_child - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) + * The return value is the RelOptInfo that's added. * * PlanRowMarks still carry the top-parent's RTI, and the top-parent's * allMarkTypes field still accumulates values from all descendents. * * "parentrte" and "parentRTindex" are immediate parent's RTE and * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. */ -static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); Oid childOID = RelationGetRelid(childrel); RangeTblEntry *childrte; Index childRTindex; AppendRelInfo *appinfo; + RelOptInfo *childrelopt; /* * Build an RTE for the child, and attach to query's rangetable list. We * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh = false. Also, set requiredPerms to zero since - * all required permissions checks are done on the original RTE. Likewise, - * set the child's securityQuals to empty, because we only want to apply - * the parent's RLS conditions regardless of what RLS properties + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties * individual children may have. (This is an intentional choice to make * inherited RLS work like regular permissions checks.) The parent * securityQuals will be propagated to children along with other base @@ -1787,55 +1874,42 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childrte_p = childrte; childrte->relid = childOID; childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) - childrte->inh = true; - else - childrte->inh = false; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); childrte->requiredPerms = 0; childrte->securityQuals = NIL; parse->rtable = lappend(parse->rtable, childrte); childRTindex = list_length(parse->rtable); *childRTindex_p = childRTindex; - /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. - */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) - { - appinfo = makeNode(AppendRelInfo); - appinfo->parent_relid = parentRTindex; - appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->rd_rel->reltype; - appinfo->child_reltype = childrel->rd_rel->reltype; - make_inh_translation_list(parentrel, childrel, childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = parentOID; - *appinfos = lappend(*appinfos, appinfo); + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + childrel->rd_att, + childOID, + childrel->rd_rel->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); } /* @@ -1867,6 +1941,24 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need need to create + * one, because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_append_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; } /* @@ -1877,14 +1969,11 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, * For paranoia's sake, we match type/collation as well as attribute name. */ static void -make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, - List **translated_vars) +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) { List *vars = NIL; - TupleDesc old_tupdesc = RelationGetDescr(oldrelation); - TupleDesc new_tupdesc = RelationGetDescr(newrelation); - Oid new_relid = RelationGetRelid(newrelation); int oldnatts = old_tupdesc->natts; int newnatts = new_tupdesc->natts; int old_attno; @@ -1914,7 +2003,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, * When we are generating the "translation list" for the parent table * of an inheritance set, no need to search for matches. */ - if (oldrelation == newrelation) + if (from_rel == to_rel) { vars = lappend(vars, makeVar(newvarno, (AttrNumber) (old_attno + 1), @@ -1940,10 +2029,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, { HeapTuple newtup; - newtup = SearchSysCacheAttName(new_relid, attname); + newtup = SearchSysCacheAttName(to_rel, attname); if (!newtup) elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; ReleaseSysCache(newtup); @@ -1953,10 +2042,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, /* Found it, check type and collation match */ if (atttypid != att->atttypid || atttypmod != att->atttypmod) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); if (attcollation != att->attcollation) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); vars = lappend(vars, makeVar(newvarno, (AttrNumber) (new_attno + 1), @@ -2023,6 +2112,255 @@ translate_col_privs(const Bitmapset *parent_privs, } /* + * build_append_child_rel + * Build a RelOptInfo for child relation of an append rel + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields base on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turns out to be self-contradictory, the + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + List *childquals; + ListCell *lc; + bool have_const_false_cq; + Index cq_min_security; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = false; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, parent->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual = (Node *) rinfo->clause; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, childqual, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might + * have securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals.) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except + * that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* + * not likely that we'd see constants here, so no + * check + */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, + false, + false, + security_level, + NULL, NULL, + NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* Set child's version of baserestrictinfo. */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; +} + +AppendRelInfo * +make_append_rel_info(RelOptInfo *parentrel, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex) +{ + AppendRelInfo *appinfo = makeNode(AppendRelInfo); + + appinfo->parent_relid = parentrel->relid; + appinfo->child_relid = childRTindex; + appinfo->parent_reltype = parentrel->reltype; + appinfo->child_reltype = childreltype; + make_inh_translation_list(parentrel->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, + &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; + + return appinfo; +} + +/* * adjust_appendrel_attrs * Copy the specified query or expression and translate Vars referring to a * parent rel to refer to the corresponding child rel instead. We also diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile index c54d0a690d..da9ccf32b4 100644 --- a/src/backend/optimizer/util/Makefile +++ b/src/backend/optimizer/util/Makefile @@ -13,6 +13,6 @@ top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global OBJS = clauses.o joininfo.o orclauses.o pathnode.o placeholder.o \ - plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o + plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index ed0953f9e1..92805046c8 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -106,7 +106,7 @@ static void set_baserel_partition_key_exprs(Relation relation, */ void get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) + Bitmapset *updatedCols, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; @@ -444,11 +444,31 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, get_relation_foreign_keys(root, rel, relation, inhparent); /* - * Collect info about relation's partitioning scheme, if any. Only - * inheritance parents may be partitioned. + * Collect some additional information for inheritance parents. */ - if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - set_relation_partition_info(root, rel, relation); + if (inhparent) + { + /* + * We'll need the TupleDesc when initializing the child relation. + * A copy is being made because concurrent changes might drop + * the relcache entry. That's possible because ALTER TABLE + * child_table NO INHERIT parent_table only requires an + * AccessShareLock on parent_table. + */ + rel->tupdesc = CreateTupleDescCopy(RelationGetDescr(relation)); + rel->reltype = RelationGetForm(relation)->reltype; + + /* + * If partitioned, also save the information of partitioning scheme, + * and whether the query updates any of the partition key columns. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + set_relation_partition_info(root, rel, relation); + root->partColsUpdated |= has_partition_attrs(relation, updatedCols, + NULL); + } + } heap_close(relation, NoLock); @@ -1854,16 +1874,20 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; + + /* + * Since we must've taken a lock on the table, it's okay to simply copy + * the pointers to relcache data here. + */ + rel->part_oids = partdesc->oids; + rel->boundinfo = partdesc->boundinfo; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 29ba19349f..c23db9d78d 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -130,6 +130,37 @@ setup_append_rel_array(PlannerInfo *root) } } +/* Expand the PlannerInfo arrays by add_size members and zero-init it. */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size = root->simple_rel_array_size + add_size; + + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + if (root->append_rel_array) + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + else + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + + /* Set the contents of just allocated memory to 0. */ + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + root->simple_rel_array_size = new_size; +} + /* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. @@ -238,7 +269,8 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte->relid, rte->inh, rte->updatedCols, + rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -279,52 +311,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); - /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. - */ - if (rte->inh) - { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); - - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; - } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); - } - return rel; } diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 35c87535d3..fcf8d6032c 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -45,6 +45,7 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/planner.h" #include "optimizer/predtest.h" @@ -436,17 +437,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -540,23 +547,20 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ @@ -564,6 +568,13 @@ prune_append_rel_partitions(RelOptInfo *rel) return NULL; /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. */ @@ -590,15 +601,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 94f14019bd..e6fdbcd030 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -15,6 +15,7 @@ #define RELATION_H #include "access/sdir.h" +#include "access/tupdesc.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/params.h" @@ -723,6 +724,7 @@ typedef struct RelOptInfo int nparts; /* number of partitions */ struct PartitionBoundInfoData *boundinfo; /* Partition bounds */ List *partition_qual; /* partition constraint */ + Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ List **partexprs; /* Non-nullable partition key expressions. */ @@ -732,6 +734,12 @@ typedef struct RelOptInfo Index inh_root_parent; /* For otherrels, this is the RT index of * inheritance table mentioned in the query * from which this relation originated */ + /* + * Set only if this is an inheritance parent relation. This information + * is needed when initializing the planning info for children. + */ + TupleDesc tupdesc; /* A "copy" of the table's tuple desriptor */ + Oid reltype; /* Table's reltype */ } RelOptInfo; /* diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 81abcf53a8..b1baa3117a 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -261,6 +261,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index 7d53cbbb87..edaf2a3b4f 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -26,7 +26,7 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); + bool inhparent, Bitmapset *updatedCols, RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 38608770a2..faae07d240 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -38,7 +38,8 @@ extern Expr *canonicalize_qual(Expr *qual, bool is_check); /* * prototypes for preptlist.c */ -extern List *preprocess_targetlist(PlannerInfo *root); +extern List *preprocess_targetlist(PlannerInfo *root, + bool inheritance_expanded); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); @@ -51,7 +52,10 @@ extern void expand_inherited_tables(PlannerInfo *root); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); - +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parentrel, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex); extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, Relids child_relids, Relids top_parent_relids); @@ -64,5 +68,4 @@ extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, Relids left_relids, Relids right_relids); extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, Relids child_relids, Relids top_parent_relids); - #endif /* PREP_H */ diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h index e07aaaf798..ac0a979010 100644 --- a/src/include/partitioning/partprune.h +++ b/src/include/partitioning/partprune.h @@ -76,7 +76,7 @@ extern PartitionPruneInfo *make_partition_pruneinfo(PlannerInfo *root, List *subpaths, List *partitioned_rels, List *prunequal); -extern Relids prune_append_rel_partitions(RelOptInfo *rel); +extern Bitmapset *prune_append_rel_partitions(RelOptInfo *rel); extern Bitmapset *get_matching_partitions(PartitionPruneContext *context, List *pruning_steps); diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 6bc106831e..1450cef057 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) -- 2.11.0
v6-0004-Move-append-expansion-code-into-its-own-file.patch (text/plain, 112.3K) download From d61e59a0f7f059f1dad6a730b4c30a383d13c877 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 7 Nov 2018 16:51:31 +0900 Subject: [PATCH v6 4/6] Move append expansion code into its own file This commit moves expand_append_rel and underlings currently in optimizer/prep/prepunionc.c to optimizer/utils/append.c. All of the AppendRelInfo based expression manipulation routines are moved to optimizer/utils/appendinfo.c. This commit only moves the code and contains no functional changes. --- src/backend/optimizer/path/allpaths.c | 2 + src/backend/optimizer/path/equivclass.c | 1 + src/backend/optimizer/path/joinrels.c | 1 + src/backend/optimizer/plan/planner.c | 1 + src/backend/optimizer/prep/prepunion.c | 1574 ------------------------------- src/backend/optimizer/util/Makefile | 5 +- src/backend/optimizer/util/append.c | 777 +++++++++++++++ src/backend/optimizer/util/appendinfo.c | 851 +++++++++++++++++ src/backend/optimizer/util/pathnode.c | 1 + src/backend/optimizer/util/relnode.c | 1 + src/backend/partitioning/partprune.c | 1 + src/include/optimizer/append.h | 25 + src/include/optimizer/appendinfo.h | 43 + src/include/optimizer/prep.h | 19 - 14 files changed, 1707 insertions(+), 1595 deletions(-) create mode 100644 src/backend/optimizer/util/append.c create mode 100644 src/backend/optimizer/util/appendinfo.c create mode 100644 src/include/optimizer/append.h create mode 100644 src/include/optimizer/appendinfo.h diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index a4dd34f2cd..d93883ed42 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -30,6 +30,8 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "optimizer/append.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/geqo.h" diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index b22b36ec0e..5ace28eab0 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -22,6 +22,7 @@ #include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index da0831de4e..6e321ec9e7 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -15,6 +15,7 @@ #include "postgres.h" #include "miscadmin.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 5801ea0854..a222bd8f1b 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -39,6 +39,7 @@ #include "nodes/print.h" #endif #include "nodes/relation.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index e4d4e7d6dd..70f37c593f 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -12,11 +12,6 @@ * case, but most of the heavy lifting for that is done elsewhere, * notably in prepjointree.c and allpaths.c. * - * There is also some code here to support planning of queries that use - * inheritance (SELECT FROM foo*). Inheritance trees are converted into - * append relations, and thenceforth share code with the UNION ALL case. - * - * * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -39,32 +34,21 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" -#include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" -#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" -#include "optimizer/var.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/selfuncs.h" #include "utils/syscache.h" -typedef struct -{ - PlannerInfo *root; - int nappinfos; - AppendRelInfo **appinfos; -} adjust_appendrel_attrs_context; - static RelOptInfo *recurse_set_operations(Node *setOp, PlannerInfo *root, List *colTypes, List *colCollations, bool junkOK, @@ -103,34 +87,6 @@ static List *generate_append_tlist(List *colTypes, List *colCollations, List *input_tlists, List *refnames_tlist); static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist); -static void expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, - RangeTblEntry *rte, Index rti); -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti, RelOptInfo *rel); -static void expand_partitioned_rtentry(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel); -static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel, - PlanRowMark *top_parentrc, Relation childrel, - RangeTblEntry **childrte_p, Index *childRTindex_p); -static void make_inh_translation_list(TupleDesc old_tupdesc, - TupleDesc new_tupdesc, - Oid from_rel, Oid to_rel, - Index newvarno, List **translated_vars); -static RelOptInfo *build_append_child_rel(PlannerInfo *root, - RelOptInfo *parent, - Index childRTindex); -static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); -static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars); -static Node *adjust_appendrel_attrs_mutator(Node *node, - adjust_appendrel_attrs_context *context); -static Relids adjust_child_relids(Relids relids, int nappinfos, - AppendRelInfo **appinfos); -static List *adjust_inherited_tlist(List *tlist, - AppendRelInfo *context); /* @@ -1467,1533 +1423,3 @@ generate_setop_grouplist(SetOperationStmt *op, List *targetlist) Assert(lg == NULL); return grouplist; } - - -/* - * expand_inherited_tables - * Expand each rangetable entry that represents an inheritance set - * into an "append relation". At the conclusion of this process, - * the "inh" flag is set in all and only those RTEs that are append - * relation parents. - */ -void -expand_inherited_tables(PlannerInfo *root) -{ - int orig_rtable_size; - Index rti; - - Assert(root->simple_rel_array_size > 0); - orig_rtable_size = root->simple_rel_array_size; - - /* - * expand_append_rtentry may add RTEs to parse->rtable. The function is - * expected to recursively handle any RTEs that it creates with inh=true. - * So just scan as far as the original end of the rtable list. - */ - for (rti = 1; rti < orig_rtable_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - RangeTblEntry *rte = root->simple_rte_array[rti]; - - /* there may be empty slots corresponding to non-baserel RTEs */ - if (brel == NULL) - continue; - - if (rte->inh) - expand_append_rtentry(root, brel, rte, rti); - } -} - -/* - * expand_append_rtentry - * This initializes RelOptInfos for an appendrel's child relations, if - * any - * - * 'rel' is the appendrel parent, whose range table entry ('rte') has been - * marked to require adding children. An appendrel parent could either - * be a subquery (if we flattened UNION ALL query) or a table that's known - * to have inheritance children. The latter consists of both regular - * inheritance parents and partitioned tables. - * - * For a subquery parent, there is not much to be done here because the - * children's RTEs are already present in the query, so we just initialize - * RelOptInfos for them. Also, the AppendRelInfos for child subqueries - * have already been added. - * - * For tables, we need to add the children to the range table and initialize - * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For - * a partitioned parent, we only add the children remaining after pruning. - * For regular inheritance parents, we find the children using - * find_all_inheritors and add all of them. - * - * If it turns out that there are no children, then we set rte->inh to false - * to let the caller know that only the parent table needs to be scanned. The - * caller can accordingly switch to a non-Append path. For a partitioned - * parent, that means an empty relation because parents themselves contain no - * data. - * - * For the regular inheritance case, the parent also gets another RTE with - * inh = false to represent it as an appendrel child. The original RTE is - * considered to represent the whole inheritance set. - */ -static void -expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, - Index rti) -{ - Assert(rte->inh); - /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ - Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); - - /* - * UNION ALL children already got RTEs and AppendRelInfos, so just build - * RelOptInfos and return. - * - * It might be a bit odd that this code is in this, because there is - * nothing to expand really. - */ - if (rte->rtekind == RTE_SUBQUERY) - { - ListCell *l; - - /* - * We don't need to use expand_planner_arrays in this case, because - * no new child RTEs are created. setup_simple_rel_arrays() and - * setup_append_rel_array would've considered these child RTEs when - * allocating space for various arrays. - */ - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst(l); - Index childRTindex = appinfo->child_relid; - - if (appinfo->parent_relid != rti) - continue; - - Assert(childRTindex < root->simple_rel_array_size); - Assert(root->simple_rte_array[childRTindex] != NULL); - - /* - * We set the correct value of baserestricinfo and - * baserestrict_min_security below. - */ - root->simple_rel_array[childRTindex] = - build_append_child_rel(root, rel, appinfo->child_relid); - } - } - else - { - Assert(rte->rtekind == RTE_RELATION); - Assert(has_subclass(rte->relid)); - - /* - * The rewriter should already have obtained an appropriate lock on - * each relation named in the query. However, for each child relation - * we add to the query, we must obtain an appropriate lock, because - * this will be the first use of those relations in the - * parse/rewrite/plan pipeline. Child rels should use the same - * lockmode as their parent. - */ - Assert(rte->rellockmode != NoLock); - - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - expand_partitioned_rtentry(root, rte, rti, rel); - else - expand_inherited_rtentry(root, rte, rti, rel); - } -} - -/* - * expand_inherited_rtentry - * Add entries for all the child tables to the query's rangetable, and - * build AppendRelInfo nodes for all the child tables and add them to - * root->append_rel_list. - * - * Note that the original RTE is considered to represent the whole - * inheritance set. The first of the generated RTEs is an RTE for the same - * table, but with inh = false, to represent the parent table in its role - * as a simple member of the inheritance set. - * - * A childless table is never considered to be an inheritance set. For - * regular inheritance, a parent RTE must always have at least two associated - * AppendRelInfos: one corresponding to the parent table as a simple member of - * inheritance set and one or more corresponding to the actual children. - */ -static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, - RelOptInfo *rel) -{ - Oid parentOID; - PlanRowMark *oldrc; - LOCKMODE lockmode = rte->rellockmode; - List *inhOIDs; - ListCell *l; - int num_children; - int num_children_added = 0; - - Assert(rte->rtekind == RTE_RELATION); - Assert(lockmode != NoLock); - parentOID = rte->relid; - - /* Scan for all members of inheritance set, acquire needed locks */ - inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); - - /* - * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite above has_subclass() check, if table - * once had a child but no longer does. - */ - num_children = list_length(inhOIDs); - if (num_children < 2) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks - * should've set isParent = true. We'll generate a new PlanRowMark for - * each child. - */ - oldrc = get_plan_rowmark(root->rowMarks, rti); - Assert(oldrc == NULL || oldrc->isParent); - - /* - * Must expand PlannerInfo arrays by num_children before we can add - * children. - */ - expand_planner_arrays(root, num_children); - - foreach(l, inhOIDs) - { - Oid childOID = lfirst_oid(l); - Relation newrelation; - RangeTblEntry *childrte; - Index childRTindex; - - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (RELATION_IS_OTHER_TEMP(newrelation)) - { - heap_close(newrelation, lockmode); - continue; - } - - (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, - newrelation, &childrte, - &childRTindex); - Assert(childrte != NULL); - /* All regular inheritance children are leaf children. */ - Assert(!childrte->inh); - Assert(childRTindex > 0); - - /* Close child relations, but keep locks */ - heap_close(newrelation, NoLock); - num_children_added++; - } - - /* - * If all children, including the parent (as child rel), were - * excluded, mark the parent rel as empty. If all the children were temp - * tables, pretend it's a non-inheritance situation; we don't need Append - * node in that case. The duplicate RTE we added for the parent table is - * harmless, so we don't bother to get rid of it; ditto for the useless - * PlanRowMark node. - */ - if (num_children_added == 0) - mark_dummy_rel(rel); - else if (num_children_added == 1) - rte->inh = false; - - /* - * Add junk columns needed by the row mark if any and also add the - * relevant expressions to the root parent's reltarget. - */ - if (oldrc) - { - List *tlist = add_rowmark_junk_columns(root, oldrc); - - build_base_rel_tlists(root, tlist); - } -} - -/* - * expand_partitioned_rtentry - * Prunes unnecessary partitions of a partitioned table and adds - * remaining ones to the Query and the PlannerInfo - * - * Partitions are added to the query in order in which they are found in - * the parent's PartitionDesc. - * - * Note: even though only the unpruned partitions will be added to the - * resulting plan, this still locks *all* partitions via find_all_inheritors - * in order to avoid partitions being locked in a different order than other - * places in the backend that may lock partitions. - */ -static void -expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel) -{ - LOCKMODE lockmode = parentrte->rellockmode; - PlanRowMark *rootrc = NULL; - int i; - Bitmapset *partindexes; - Index rootParentRTindex = parentrel->inh_root_parent > 0 ? - parentrel->inh_root_parent : - parentRTindex; - - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - - /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, values of the - * indexes of partitioned relations that appear down in the tree will be - * bubbled up into root parent's list so that when we've created Paths for - * all the children, the root table's list will contain all such indexes. - */ - parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - - /* Perform pruning. */ - partindexes = prune_append_rel_partitions(parentrel); - - /* Must expand PlannerInfo arrays before we can add children. */ - expand_planner_arrays(root, bms_num_members(partindexes)); - - /* - * For partitioned tables, we also store the partition RelOptInfo - * pointers in the parent's RelOptInfo. - */ - parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * - parentrel->nparts); - - rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); - Assert(rootrc == NULL || rootrc->isParent); - i = -1; - while ((i = bms_next_member(partindexes, i)) >= 0) - { - Oid childOID = parentrel->part_oids[i]; - Relation newrelation; - RelOptInfo *childrel; - RangeTblEntry *childrte; - Index childRTindex; - - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); - Assert(!RELATION_IS_OTHER_TEMP(newrelation)); - - /* - * A partitioned child table with 0 children is a dummy rel, so don't - * bother creating planner objects for it. - */ - if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && - RelationGetPartitionDesc(newrelation)->nparts == 0) - { - heap_close(newrelation, NoLock); - continue; - } - - childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, - parentrel, rootrc, newrelation, - &childrte, &childRTindex); - Assert(childrel != NULL); - parentrel->part_rels[i] = childrel; - - /* Close child relations, but keep locks */ - heap_close(newrelation, NoLock); - - /* If the child is partitioned itself, expand it too. */ - if (childrel->part_scheme) - { - Assert(childrte->inh); - expand_partitioned_rtentry(root, childrte, childRTindex, - childrel); - } - } - - /* - * Add junk columns needed by the row mark if any and also add the - * relevant expressions to the root parent's reltarget. - */ - if (rootrc) - { - List *tlist = add_rowmark_junk_columns(root, rootrc); - - build_base_rel_tlists(root, tlist); - } -} - -/* - * add_inheritance_child_rel - * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally - * a RelOptInfo for an inheritance child relation. - * - * The return value is the RelOptInfo that's added. - * - * PlanRowMarks still carry the top-parent's RTI, and the top-parent's - * allMarkTypes field still accumulates values from all descendents. - * - * "parentrte" and "parentRTindex" are immediate parent's RTE and - * RTI. "top_parentrc" is top parent's PlanRowMark. - */ -static RelOptInfo * -add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel, - PlanRowMark *top_parentrc, Relation childrel, - RangeTblEntry **childrte_p, Index *childRTindex_p) -{ - Query *parse = root->parse; - Oid childOID = RelationGetRelid(childrel); - RangeTblEntry *childrte; - Index childRTindex; - AppendRelInfo *appinfo; - RelOptInfo *childrelopt; - - /* - * Build an RTE for the child, and attach to query's rangetable list. We - * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh appropriately. Also, set requiredPerms to zero - * since all required permissions checks are done on the original RTE. - * Likewise, set the child's securityQuals to empty, because we only want - * to apply the parent's RLS conditions regardless of what RLS properties - * individual children may have. (This is an intentional choice to make - * inherited RLS work like regular permissions checks.) The parent - * securityQuals will be propagated to children along with other base - * restriction clauses, so we don't need to do it here. - */ - childrte = copyObject(parentrte); - *childrte_p = childrte; - childrte->relid = childOID; - childrte->relkind = childrel->rd_rel->relkind; - /* - * A partitioned child will need to be expanded as an append parent - * itself, so set its inh to true. - */ - childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); - childrte->requiredPerms = 0; - childrte->securityQuals = NIL; - parse->rtable = lappend(parse->rtable, childrte); - childRTindex = list_length(parse->rtable); - *childRTindex_p = childRTindex; - - /* Create an AppendRelInfo and add it to planner's global list. */ - appinfo = make_append_rel_info(parentrel, parentrte, - childrel->rd_att, - childOID, - childrel->rd_rel->reltype, - childRTindex); - root->append_rel_list = lappend(root->append_rel_list, appinfo); - - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childrte->relid != parentrte->relid) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } - - /* - * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE. - */ - if (top_parentrc) - { - PlanRowMark *childrc = makeNode(PlanRowMark); - - childrc->rti = childRTindex; - childrc->prti = top_parentrc->rti; - childrc->rowmarkId = top_parentrc->rowmarkId; - /* Reselect rowmark type, because relkind might not match parent */ - childrc->markType = select_rowmark_type(childrte, - top_parentrc->strength); - childrc->allMarkTypes = (1 << childrc->markType); - childrc->strength = top_parentrc->strength; - childrc->waitPolicy = top_parentrc->waitPolicy; - - /* - * We mark RowMarks for partitioned child tables as parent RowMarks so - * that the executor ignores them (except their existence means that - * the child tables be locked using appropriate mode). - */ - childrc->isParent = (childrte->relkind == RELKIND_PARTITIONED_TABLE); - - /* Include child's rowmark type in top parent's allMarkTypes */ - top_parentrc->allMarkTypes |= childrc->allMarkTypes; - - root->rowMarks = lappend(root->rowMarks, childrc); - } - - /* - * Add the RelOptInfo. Even though we may not really scan this relation - * for reasons such as contradictory quals, we still need need to create - * one, because for every RTE in the query's range table, there must be an - * accompanying RelOptInfo. - */ - - /* First, store the RTE and appinfos into planner arrays. */ - Assert(root->simple_rte_array[childRTindex] == NULL); - root->simple_rte_array[childRTindex] = childrte; - Assert(root->append_rel_array[childRTindex] == NULL); - root->append_rel_array[childRTindex] = appinfo; - - childrelopt = build_append_child_rel(root, parentrel, childRTindex); - Assert(childrelopt != NULL); - - return childrelopt; -} - -/* - * make_inh_translation_list - * Build the list of translations from parent Vars to child Vars for - * an inheritance child. - * - * For paranoia's sake, we match type/collation as well as attribute name. - */ -static void -make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, - Oid from_rel, Oid to_rel, - Index newvarno, List **translated_vars) -{ - List *vars = NIL; - int oldnatts = old_tupdesc->natts; - int newnatts = new_tupdesc->natts; - int old_attno; - int new_attno = 0; - - for (old_attno = 0; old_attno < oldnatts; old_attno++) - { - Form_pg_attribute att; - char *attname; - Oid atttypid; - int32 atttypmod; - Oid attcollation; - - att = TupleDescAttr(old_tupdesc, old_attno); - if (att->attisdropped) - { - /* Just put NULL into this list entry */ - vars = lappend(vars, NULL); - continue; - } - attname = NameStr(att->attname); - atttypid = att->atttypid; - atttypmod = att->atttypmod; - attcollation = att->attcollation; - - /* - * When we are generating the "translation list" for the parent table - * of an inheritance set, no need to search for matches. - */ - if (from_rel == to_rel) - { - vars = lappend(vars, makeVar(newvarno, - (AttrNumber) (old_attno + 1), - atttypid, - atttypmod, - attcollation, - 0)); - continue; - } - - /* - * Otherwise we have to search for the matching column by name. - * There's no guarantee it'll have the same column position, because - * of cases like ALTER TABLE ADD COLUMN and multiple inheritance. - * However, in simple cases, the relative order of columns is mostly - * the same in both relations, so try the column of newrelation that - * follows immediately after the one that we just found, and if that - * fails, let syscache handle it. - */ - if (new_attno >= newnatts || - (att = TupleDescAttr(new_tupdesc, new_attno))->attisdropped || - strcmp(attname, NameStr(att->attname)) != 0) - { - HeapTuple newtup; - - newtup = SearchSysCacheAttName(to_rel, attname); - if (!newtup) - elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, get_rel_name(to_rel)); - new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; - ReleaseSysCache(newtup); - - att = TupleDescAttr(new_tupdesc, new_attno); - } - - /* Found it, check type and collation match */ - if (atttypid != att->atttypid || atttypmod != att->atttypmod) - elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, get_rel_name(to_rel)); - if (attcollation != att->attcollation) - elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, get_rel_name(to_rel)); - - vars = lappend(vars, makeVar(newvarno, - (AttrNumber) (new_attno + 1), - atttypid, - atttypmod, - attcollation, - 0)); - new_attno++; - } - - *translated_vars = vars; -} - -/* - * translate_col_privs - * Translate a bitmapset representing per-column privileges from the - * parent rel's attribute numbering to the child's. - * - * The only surprise here is that we don't translate a parent whole-row - * reference into a child whole-row reference. That would mean requiring - * permissions on all child columns, which is overly strict, since the - * query is really only going to reference the inherited columns. Instead - * we set the per-column bits for all inherited columns. - */ -static Bitmapset * -translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars) -{ - Bitmapset *child_privs = NULL; - bool whole_row; - int attno; - ListCell *lc; - - /* System attributes have the same numbers in all tables */ - for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) - { - if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - attno - FirstLowInvalidHeapAttributeNumber); - } - - /* Check if parent has whole-row reference */ - whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, - parent_privs); - - /* And now translate the regular user attributes, using the vars list */ - attno = InvalidAttrNumber; - foreach(lc, translated_vars) - { - Var *var = lfirst_node(Var, lc); - - attno++; - if (var == NULL) /* ignore dropped columns */ - continue; - if (whole_row || - bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - var->varattno - FirstLowInvalidHeapAttributeNumber); - } - - return child_privs; -} - -/* - * build_append_child_rel - * Build a RelOptInfo for child relation of an append rel - * - * After creating the RelOptInfo for the given child RT index, it goes on to - * initialize some of its fields base on the parent RelOptInfo. - * - * If the quals in baserestrictinfo turns out to be self-contradictory, the - * RelOptInfo is marked dummy before returning. - */ -static RelOptInfo * -build_append_child_rel(PlannerInfo *root, - RelOptInfo *parent, - Index childRTindex) -{ - RelOptInfo *childrel; - RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; - AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; - List *childquals; - ListCell *lc; - bool have_const_false_cq; - Index cq_min_security; - - /* Build the RelOptInfo. */ - childrel = build_simple_rel(root, childRTindex, parent); - - /* - * Propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - */ - childrel->direct_lateral_relids = parent->direct_lateral_relids; - childrel->lateral_relids = parent->lateral_relids; - childrel->lateral_referencers = parent->lateral_referencers; - - /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) - */ - childquals = false; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, parent->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual = (Node *) rinfo->clause; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, childqual, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might - * have securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals.) Pull any such securityQuals up into the - * baserestrictinfo for the child. This is similar to - * process_security_barrier_quals() for the parent rel, except - * that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* - * not likely that we'd see constants here, so no - * check - */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, - false, - false, - security_level, - NULL, NULL, - NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* Set child's version of baserestrictinfo. */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - } - - return childrel; -} - -/* - * add_rowmark_junk_columns - * Add necessary junk columns for rowmarked inheritance parent rel. - * - * These values are needed for locking of rels selected FOR UPDATE/SHARE, and - * to do EvalPlanQual rechecking. See comments for PlanRowMark in - * plannodes.h. - */ -static List * -add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) -{ - List *tlist = root->processed_tlist; - Var *var; - char resname[32]; - TargetEntry *tle; - - if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) - { - /* Need to fetch TID */ - var = makeVar(rc->rti, - SelfItemPointerAttributeNumber, - TIDOID, - -1, - InvalidOid, - 0); - snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - } - if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) - { - /* Need the whole row as a junk var */ - var = makeWholeRowVar(root->simple_rte_array[rc->rti], - rc->rti, - 0, - false); - snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - } - - /* For inheritance cases, always fetch the tableoid too. */ - var = makeVar(rc->rti, - TableOidAttributeNumber, - OIDOID, - -1, - InvalidOid, - 0); - snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - - return tlist; -} - -AppendRelInfo * -make_append_rel_info(RelOptInfo *parentrel, RangeTblEntry *parentrte, - TupleDesc childdesc, Oid childoid, Oid childreltype, - Index childRTindex) -{ - AppendRelInfo *appinfo = makeNode(AppendRelInfo); - - appinfo->parent_relid = parentrel->relid; - appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->reltype; - appinfo->child_reltype = childreltype; - make_inh_translation_list(parentrel->tupdesc, childdesc, - parentrte->relid, childoid, - childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = parentrte->relid; - - return appinfo; -} - -/* - * adjust_appendrel_attrs - * Copy the specified query or expression and translate Vars referring to a - * parent rel to refer to the corresponding child rel instead. We also - * update rtindexes appearing outside Vars, such as resultRelation and - * jointree relids. - * - * Note: this is only applied after conversion of sublinks to subplans, - * so we don't need to cope with recursion into sub-queries. - * - * Note: this is not hugely different from what pullup_replace_vars() does; - * maybe we should try to fold the two routines together. - */ -Node * -adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, - AppendRelInfo **appinfos) -{ - Node *result; - adjust_appendrel_attrs_context context; - - context.root = root; - context.nappinfos = nappinfos; - context.appinfos = appinfos; - - /* If there's nothing to adjust, don't call this function. */ - Assert(nappinfos >= 1 && appinfos != NULL); - - /* - * Must be prepared to start with a Query or a bare expression tree. - */ - if (node && IsA(node, Query)) - { - Query *newnode; - int cnt; - - newnode = query_tree_mutator((Query *) node, - adjust_appendrel_attrs_mutator, - (void *) &context, - QTW_IGNORE_RC_SUBQUERIES); - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (newnode->resultRelation == appinfo->parent_relid) - { - newnode->resultRelation = appinfo->child_relid; - /* Fix tlist resnos too, if it's inherited UPDATE */ - if (newnode->commandType == CMD_UPDATE) - newnode->targetList = - adjust_inherited_tlist(newnode->targetList, - appinfo); - break; - } - } - - result = (Node *) newnode; - } - else - result = adjust_appendrel_attrs_mutator(node, &context); - - return result; -} - -static Node * -adjust_appendrel_attrs_mutator(Node *node, - adjust_appendrel_attrs_context *context) -{ - AppendRelInfo **appinfos = context->appinfos; - int nappinfos = context->nappinfos; - int cnt; - - if (node == NULL) - return NULL; - if (IsA(node, Var)) - { - Var *var = (Var *) copyObject(node); - AppendRelInfo *appinfo = NULL; - - for (cnt = 0; cnt < nappinfos; cnt++) - { - if (var->varno == appinfos[cnt]->parent_relid) - { - appinfo = appinfos[cnt]; - break; - } - } - - if (var->varlevelsup == 0 && appinfo) - { - var->varno = appinfo->child_relid; - var->varnoold = appinfo->child_relid; - if (var->varattno > 0) - { - Node *newnode; - - if (var->varattno > list_length(appinfo->translated_vars)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - var->varattno, get_rel_name(appinfo->parent_reloid)); - newnode = copyObject(list_nth(appinfo->translated_vars, - var->varattno - 1)); - if (newnode == NULL) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - var->varattno, get_rel_name(appinfo->parent_reloid)); - return newnode; - } - else if (var->varattno == 0) - { - /* - * Whole-row Var: if we are dealing with named rowtypes, we - * can use a whole-row Var for the child table plus a coercion - * step to convert the tuple layout to the parent's rowtype. - * Otherwise we have to generate a RowExpr. - */ - if (OidIsValid(appinfo->child_reltype)) - { - Assert(var->vartype == appinfo->parent_reltype); - if (appinfo->parent_reltype != appinfo->child_reltype) - { - ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr); - - r->arg = (Expr *) var; - r->resulttype = appinfo->parent_reltype; - r->convertformat = COERCE_IMPLICIT_CAST; - r->location = -1; - /* Make sure the Var node has the right type ID, too */ - var->vartype = appinfo->child_reltype; - return (Node *) r; - } - } - else - { - /* - * Build a RowExpr containing the translated variables. - * - * In practice var->vartype will always be RECORDOID here, - * so we need to come up with some suitable column names. - * We use the parent RTE's column names. - * - * Note: we can't get here for inheritance cases, so there - * is no need to worry that translated_vars might contain - * some dummy NULLs. - */ - RowExpr *rowexpr; - List *fields; - RangeTblEntry *rte; - - rte = rt_fetch(appinfo->parent_relid, - context->root->parse->rtable); - fields = copyObject(appinfo->translated_vars); - rowexpr = makeNode(RowExpr); - rowexpr->args = fields; - rowexpr->row_typeid = var->vartype; - rowexpr->row_format = COERCE_IMPLICIT_CAST; - rowexpr->colnames = copyObject(rte->eref->colnames); - rowexpr->location = -1; - - return (Node *) rowexpr; - } - } - /* system attributes don't need any other translation */ - } - return (Node *) var; - } - if (IsA(node, CurrentOfExpr)) - { - CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node); - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (cexpr->cvarno == appinfo->parent_relid) - { - cexpr->cvarno = appinfo->child_relid; - break; - } - } - return (Node *) cexpr; - } - if (IsA(node, RangeTblRef)) - { - RangeTblRef *rtr = (RangeTblRef *) copyObject(node); - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (rtr->rtindex == appinfo->parent_relid) - { - rtr->rtindex = appinfo->child_relid; - break; - } - } - return (Node *) rtr; - } - if (IsA(node, JoinExpr)) - { - /* Copy the JoinExpr node with correct mutation of subnodes */ - JoinExpr *j; - AppendRelInfo *appinfo; - - j = (JoinExpr *) expression_tree_mutator(node, - adjust_appendrel_attrs_mutator, - (void *) context); - /* now fix JoinExpr's rtindex (probably never happens) */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - appinfo = appinfos[cnt]; - - if (j->rtindex == appinfo->parent_relid) - { - j->rtindex = appinfo->child_relid; - break; - } - } - return (Node *) j; - } - if (IsA(node, PlaceHolderVar)) - { - /* Copy the PlaceHolderVar node with correct mutation of subnodes */ - PlaceHolderVar *phv; - - phv = (PlaceHolderVar *) expression_tree_mutator(node, - adjust_appendrel_attrs_mutator, - (void *) context); - /* now fix PlaceHolderVar's relid sets */ - if (phv->phlevelsup == 0) - phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos, - context->appinfos); - return (Node *) phv; - } - - /* - * This is needed, because inheritance_make_rel_from_joinlist needs to - * translate root->join_info_list executing make_rel_from_joinlist for a - * given child. - */ - if (IsA(node, SpecialJoinInfo)) - { - SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; - SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); - - memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); - newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, - context->nappinfos, - context->appinfos); - newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, - context->nappinfos, - context->appinfos); - newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, - context->nappinfos, - context->appinfos); - newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, - context->nappinfos, - context->appinfos); - newinfo->semi_rhs_exprs = - (List *) expression_tree_mutator((Node *) - oldinfo->semi_rhs_exprs, - adjust_appendrel_attrs_mutator, - (void *) context); - return (Node *) newinfo; - } - - /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, AppendRelInfo)); - Assert(!IsA(node, PlaceHolderInfo)); - Assert(!IsA(node, MinMaxAggInfo)); - - /* - * We have to process RestrictInfo nodes specially. (Note: although - * set_append_rel_pathlist will hide RestrictInfos in the parent's - * baserestrictinfo list from us, it doesn't hide those in joininfo.) - */ - if (IsA(node, RestrictInfo)) - { - RestrictInfo *oldinfo = (RestrictInfo *) node; - RestrictInfo *newinfo = makeNode(RestrictInfo); - - /* Copy all flat-copiable fields */ - memcpy(newinfo, oldinfo, sizeof(RestrictInfo)); - - /* Recursively fix the clause itself */ - newinfo->clause = (Expr *) - adjust_appendrel_attrs_mutator((Node *) oldinfo->clause, context); - - /* and the modified version, if an OR clause */ - newinfo->orclause = (Expr *) - adjust_appendrel_attrs_mutator((Node *) oldinfo->orclause, context); - - /* adjust relid sets too */ - newinfo->clause_relids = adjust_child_relids(oldinfo->clause_relids, - context->nappinfos, - context->appinfos); - newinfo->required_relids = adjust_child_relids(oldinfo->required_relids, - context->nappinfos, - context->appinfos); - newinfo->outer_relids = adjust_child_relids(oldinfo->outer_relids, - context->nappinfos, - context->appinfos); - newinfo->nullable_relids = adjust_child_relids(oldinfo->nullable_relids, - context->nappinfos, - context->appinfos); - newinfo->left_relids = adjust_child_relids(oldinfo->left_relids, - context->nappinfos, - context->appinfos); - newinfo->right_relids = adjust_child_relids(oldinfo->right_relids, - context->nappinfos, - context->appinfos); - - /* - * Reset cached derivative fields, since these might need to have - * different values when considering the child relation. Note we - * don't reset left_ec/right_ec: each child variable is implicitly - * equivalent to its parent, so still a member of the same EC if any. - */ - newinfo->eval_cost.startup = -1; - newinfo->norm_selec = -1; - newinfo->outer_selec = -1; - newinfo->left_em = NULL; - newinfo->right_em = NULL; - newinfo->scansel_cache = NIL; - newinfo->left_bucketsize = -1; - newinfo->right_bucketsize = -1; - newinfo->left_mcvfreq = -1; - newinfo->right_mcvfreq = -1; - - return (Node *) newinfo; - } - - /* - * NOTE: we do not need to recurse into sublinks, because they should - * already have been converted to subplans before we see them. - */ - Assert(!IsA(node, SubLink)); - Assert(!IsA(node, Query)); - - return expression_tree_mutator(node, adjust_appendrel_attrs_mutator, - (void *) context); -} - -/* - * Substitute child relids for parent relids in a Relid set. The array of - * appinfos specifies the substitutions to be performed. - */ -static Relids -adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos) -{ - Bitmapset *result = NULL; - int cnt; - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - /* Remove parent, add child */ - if (bms_is_member(appinfo->parent_relid, relids)) - { - /* Make a copy if we are changing the set. */ - if (!result) - result = bms_copy(relids); - - result = bms_del_member(result, appinfo->parent_relid); - result = bms_add_member(result, appinfo->child_relid); - } - } - - /* If we made any changes, return the modified copy. */ - if (result) - return result; - - /* Otherwise, return the original set without modification. */ - return relids; -} - -/* - * Replace any relid present in top_parent_relids with its child in - * child_relids. Members of child_relids can be multiple levels below top - * parent in the partition hierarchy. - */ -Relids -adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, - Relids child_relids, Relids top_parent_relids) -{ - AppendRelInfo **appinfos; - int nappinfos; - Relids parent_relids = NULL; - Relids result; - Relids tmp_result = NULL; - int cnt; - - /* - * If the given relids set doesn't contain any of the top parent relids, - * it will remain unchanged. - */ - if (!bms_overlap(relids, top_parent_relids)) - return relids; - - appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); - - /* Construct relids set for the immediate parent of the given child. */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); - } - - /* Recurse if immediate parent is not the top parent. */ - if (!bms_equal(parent_relids, top_parent_relids)) - { - tmp_result = adjust_child_relids_multilevel(root, relids, - parent_relids, - top_parent_relids); - relids = tmp_result; - } - - result = adjust_child_relids(relids, nappinfos, appinfos); - - /* Free memory consumed by any intermediate result. */ - if (tmp_result) - bms_free(tmp_result); - bms_free(parent_relids); - pfree(appinfos); - - return result; -} - -/* - * Adjust the targetlist entries of an inherited UPDATE operation - * - * The expressions have already been fixed, but we have to make sure that - * the target resnos match the child table (they may not, in the case of - * a column that was added after-the-fact by ALTER TABLE). In some cases - * this can force us to re-order the tlist to preserve resno ordering. - * (We do all this work in special cases so that preptlist.c is fast for - * the typical case.) - * - * The given tlist has already been through expression_tree_mutator; - * therefore the TargetEntry nodes are fresh copies that it's okay to - * scribble on. - * - * Note that this is not needed for INSERT because INSERT isn't inheritable. - */ -static List * -adjust_inherited_tlist(List *tlist, AppendRelInfo *context) -{ - bool changed_it = false; - ListCell *tl; - List *new_tlist; - bool more; - int attrno; - - /* This should only happen for an inheritance case, not UNION ALL */ - Assert(OidIsValid(context->parent_reloid)); - - /* Scan tlist and update resnos to match attnums of child rel */ - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - Var *childvar; - - if (tle->resjunk) - continue; /* ignore junk items */ - - /* Look up the translation of this column: it must be a Var */ - if (tle->resno <= 0 || - tle->resno > list_length(context->translated_vars)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - tle->resno, get_rel_name(context->parent_reloid)); - childvar = (Var *) list_nth(context->translated_vars, tle->resno - 1); - if (childvar == NULL || !IsA(childvar, Var)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - tle->resno, get_rel_name(context->parent_reloid)); - - if (tle->resno != childvar->varattno) - { - tle->resno = childvar->varattno; - changed_it = true; - } - } - - /* - * If we changed anything, re-sort the tlist by resno, and make sure - * resjunk entries have resnos above the last real resno. The sort - * algorithm is a bit stupid, but for such a seldom-taken path, small is - * probably better than fast. - */ - if (!changed_it) - return tlist; - - new_tlist = NIL; - more = true; - for (attrno = 1; more; attrno++) - { - more = false; - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - - if (tle->resjunk) - continue; /* ignore junk items */ - - if (tle->resno == attrno) - new_tlist = lappend(new_tlist, tle); - else if (tle->resno > attrno) - more = true; - } - } - - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - - if (!tle->resjunk) - continue; /* here, ignore non-junk items */ - - tle->resno = attrno; - new_tlist = lappend(new_tlist, tle); - attrno++; - } - - return new_tlist; -} - -/* - * adjust_appendrel_attrs_multilevel - * Apply Var translations from a toplevel appendrel parent down to a child. - * - * In some cases we need to translate expressions referencing a parent relation - * to reference an appendrel child that's multiple levels removed from it. - */ -Node * -adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, - Relids child_relids, - Relids top_parent_relids) -{ - AppendRelInfo **appinfos; - Bitmapset *parent_relids = NULL; - int nappinfos; - int cnt; - - Assert(bms_num_members(child_relids) == bms_num_members(top_parent_relids)); - - appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); - - /* Construct relids set for the immediate parent of given child. */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); - } - - /* Recurse if immediate parent is not the top parent. */ - if (!bms_equal(parent_relids, top_parent_relids)) - node = adjust_appendrel_attrs_multilevel(root, node, parent_relids, - top_parent_relids); - - /* Now translate for this child */ - node = adjust_appendrel_attrs(root, node, nappinfos, appinfos); - - pfree(appinfos); - - return node; -} - -/* - * Construct the SpecialJoinInfo for a child-join by translating - * SpecialJoinInfo for the join between parents. left_relids and right_relids - * are the relids of left and right side of the join respectively. - */ -SpecialJoinInfo * -build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, - Relids left_relids, Relids right_relids) -{ - SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo); - AppendRelInfo **left_appinfos; - int left_nappinfos; - AppendRelInfo **right_appinfos; - int right_nappinfos; - - memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo)); - left_appinfos = find_appinfos_by_relids(root, left_relids, - &left_nappinfos); - right_appinfos = find_appinfos_by_relids(root, right_relids, - &right_nappinfos); - - sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand, - left_nappinfos, left_appinfos); - sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand, - right_nappinfos, - right_appinfos); - sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand, - left_nappinfos, left_appinfos); - sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand, - right_nappinfos, - right_appinfos); - sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root, - (Node *) sjinfo->semi_rhs_exprs, - right_nappinfos, - right_appinfos); - - pfree(left_appinfos); - pfree(right_appinfos); - - return sjinfo; -} - -/* - * find_appinfos_by_relids - * Find AppendRelInfo structures for all relations specified by relids. - * - * The AppendRelInfos are returned in an array, which can be pfree'd by the - * caller. *nappinfos is set to the number of entries in the array. - */ -AppendRelInfo ** -find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos) -{ - AppendRelInfo **appinfos; - int cnt = 0; - int i; - - *nappinfos = bms_num_members(relids); - appinfos = (AppendRelInfo **) palloc(sizeof(AppendRelInfo *) * *nappinfos); - - i = -1; - while ((i = bms_next_member(relids, i)) >= 0) - { - AppendRelInfo *appinfo = root->append_rel_array[i]; - - if (!appinfo) - elog(ERROR, "child rel %d not found in append_rel_array", i); - - appinfos[cnt++] = appinfo; - } - return appinfos; -} diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile index da9ccf32b4..d166030a10 100644 --- a/src/backend/optimizer/util/Makefile +++ b/src/backend/optimizer/util/Makefile @@ -12,7 +12,8 @@ subdir = src/backend/optimizer/util top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = clauses.o joininfo.o orclauses.o pathnode.o placeholder.o \ - plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o +OBJS = append.o appendinfo.o clauses.o joininfo.o orclauses.o pathnode.o \ + placeholder.o plancat.o predtest.o relnode.o restrictinfo.o tlist.o \ + var.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/optimizer/util/append.c b/src/backend/optimizer/util/append.c new file mode 100644 index 0000000000..d2ad80034f --- /dev/null +++ b/src/backend/optimizer/util/append.c @@ -0,0 +1,777 @@ +/*------------------------------------------------------------------------- + * + * append.c + * Routines to process children of an appendrel parent + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/optimizer/path/append.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <limits.h> + +#include "access/sysattr.h" +#include "catalog/partition.h" +#include "catalog/pg_class.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_type.h" +#include "nodes/makefuncs.h" +#include "optimizer/append.h" +#include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" +#include "optimizer/plancat.h" +#include "optimizer/planmain.h" +#include "optimizer/planner.h" +#include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" +#include "optimizer/tlist.h" +#include "optimizer/var.h" +#include "partitioning/partprune.h" +#include "utils/rel.h" + +static void expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti); +static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, + Index rti, RelOptInfo *rel); +static void expand_partitioned_rtentry(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p); +static RelOptInfo *build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); + + +/* + * expand_inherited_tables + * Expand each rangetable entry that represents an inheritance set + * into an "append relation". At the conclusion of this process, + * the "inh" flag is set in all and only those RTEs that are append + * relation parents. + */ +void +expand_inherited_tables(PlannerInfo *root) +{ + int orig_rtable_size; + Index rti; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; + + /* + * expand_append_rtentry may add RTEs to parse->rtable. The function is + * expected to recursively handle any RTEs that it creates with inh=true. + * So just scan as far as the original end of the rtable list. + */ + for (rti = 1; rti < orig_rtable_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; + + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + if (rte->inh) + expand_append_rtentry(root, brel, rte, rti); + } +} + +/* + * expand_append_rtentry + * This initializes RelOptInfos for an appendrel's child relations, if + * any + * + * 'rel' is the appendrel parent, whose range table entry ('rte') has been + * marked to require adding children. An appendrel parent could either + * be a subquery (if we flattened UNION ALL query) or a table that's known + * to have inheritance children. The latter consists of both regular + * inheritance parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as an appendrel child. The original RTE is + * considered to represent the whole inheritance set. + */ +static void +expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, + Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_append_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_inherited_rtentry(root, rte, rti, rel); + } +} + +/* + * expand_inherited_rtentry + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. + * + * Note that the original RTE is considered to represent the whole + * inheritance set. The first of the generated RTEs is an RTE for the same + * table, but with inh = false, to represent the parent table in its role + * as a simple member of the inheritance set. + * + * A childless table is never considered to be an inheritance set. For + * regular inheritance, a parent RTE must always have at least two associated + * AppendRelInfos: one corresponding to the parent table as a simple member of + * inheritance set and one or more corresponding to the actual children. + */ +static void +expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, + RelOptInfo *rel) +{ + Oid parentOID; + PlanRowMark *oldrc; + LOCKMODE lockmode = rte->rellockmode; + List *inhOIDs; + ListCell *l; + int num_children; + int num_children_added = 0; + + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); + parentOID = rte->relid; + + /* Scan for all members of inheritance set, acquire needed locks */ + inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); + + /* + * Check that there's at least one descendant, else treat as no-child + * case. This could happen despite above has_subclass() check, if table + * once had a child but no longer does. + */ + num_children = list_length(inhOIDs); + if (num_children < 2) + { + /* Clear flag before returning */ + rte->inh = false; + return; + } + + /* + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. + */ + oldrc = get_plan_rowmark(root->rowMarks, rti); + Assert(oldrc == NULL || oldrc->isParent); + + /* + * Must expand PlannerInfo arrays by num_children before we can add + * children. + */ + expand_planner_arrays(root, num_children); + + foreach(l, inhOIDs) + { + Oid childOID = lfirst_oid(l); + Relation newrelation; + RangeTblEntry *childrte; + Index childRTindex; + + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + + /* + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. + */ + if (RELATION_IS_OTHER_TEMP(newrelation)) + { + heap_close(newrelation, lockmode); + continue; + } + + (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation, &childrte, + &childRTindex); + Assert(childrte != NULL); + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + Assert(childRTindex > 0); + + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; + } + + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } +} + +/* + * expand_partitioned_rtentry + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * in order to avoid partitions being locked in a different order than other + * places in the backend that may lock partitions. + */ +static void +expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel) +{ + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; + int i; + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; + + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); + + /* + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. + */ + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); + + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + expand_planner_arrays(root, bms_num_members(partindexes)); + + /* + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. + */ + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); + + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) + { + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + RangeTblEntry *childrte; + Index childRTindex; + + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); + + /* + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. + */ + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } + + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation, + &childrte, &childRTindex); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; + + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + + /* If the child is partitioned itself, expand it too. */ + if (childrel->part_scheme) + { + Assert(childrte->inh); + expand_partitioned_rtentry(root, childrte, childRTindex, + childrel); + } + } + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); + } +} + +/* + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. + * + * The return value is the RelOptInfo that's added. + * + * PlanRowMarks still carry the top-parent's RTI, and the top-parent's + * allMarkTypes field still accumulates values from all descendents. + * + * "parentrte" and "parentRTindex" are immediate parent's RTE and + * RTI. "top_parentrc" is top parent's PlanRowMark. + */ +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p) +{ + Query *parse = root->parse; + Oid childOID = RelationGetRelid(childrel); + RangeTblEntry *childrte; + Index childRTindex; + AppendRelInfo *appinfo; + RelOptInfo *childrelopt; + + /* + * Build an RTE for the child, and attach to query's rangetable list. We + * copy most fields of the parent's RTE, but replace relation OID and + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties + * individual children may have. (This is an intentional choice to make + * inherited RLS work like regular permissions checks.) The parent + * securityQuals will be propagated to children along with other base + * restriction clauses, so we don't need to do it here. + */ + childrte = copyObject(parentrte); + *childrte_p = childrte; + childrte->relid = childOID; + childrte->relkind = childrel->rd_rel->relkind; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); + childrte->requiredPerms = 0; + childrte->securityQuals = NIL; + parse->rtable = lappend(parse->rtable, childrte); + childRTindex = list_length(parse->rtable); + *childRTindex_p = childRTindex; + + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + childrel->rd_att, + childOID, + childrel->rd_rel->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); + + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); + } + + /* + * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE. + */ + if (top_parentrc) + { + PlanRowMark *childrc = makeNode(PlanRowMark); + + childrc->rti = childRTindex; + childrc->prti = top_parentrc->rti; + childrc->rowmarkId = top_parentrc->rowmarkId; + /* Reselect rowmark type, because relkind might not match parent */ + childrc->markType = select_rowmark_type(childrte, + top_parentrc->strength); + childrc->allMarkTypes = (1 << childrc->markType); + childrc->strength = top_parentrc->strength; + childrc->waitPolicy = top_parentrc->waitPolicy; + + /* + * We mark RowMarks for partitioned child tables as parent RowMarks so + * that the executor ignores them (except their existence means that + * the child tables be locked using appropriate mode). + */ + childrc->isParent = (childrte->relkind == RELKIND_PARTITIONED_TABLE); + + /* Include child's rowmark type in top parent's allMarkTypes */ + top_parentrc->allMarkTypes |= childrc->allMarkTypes; + + root->rowMarks = lappend(root->rowMarks, childrc); + } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need need to create + * one, because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_append_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; +} + +/* + * build_append_child_rel + * Build a RelOptInfo for child relation of an append rel + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields base on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turns out to be self-contradictory, the + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + List *childquals; + ListCell *lc; + bool have_const_false_cq; + Index cq_min_security; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = false; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, parent->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual = (Node *) rinfo->clause; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, childqual, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might + * have securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals.) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except + * that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* + * not likely that we'd see constants here, so no + * check + */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, + false, + false, + security_level, + NULL, NULL, + NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* Set child's version of baserestrictinfo. */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; +} diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c new file mode 100644 index 0000000000..9502535e53 --- /dev/null +++ b/src/backend/optimizer/util/appendinfo.c @@ -0,0 +1,851 @@ +/*------------------------------------------------------------------------- + * + * appendinfo.c + * Routines for mapping between append parent(s) and children + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/optimizer/path/appendinfo.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" +#include "parser/parsetree.h" +#include "utils/rel.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" + +typedef struct +{ + PlannerInfo *root; + int nappinfos; + AppendRelInfo **appinfos; +} adjust_appendrel_attrs_context; + +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); +static Node *adjust_appendrel_attrs_mutator(Node *node, + adjust_appendrel_attrs_context *context); +static Relids adjust_child_relids(Relids relids, int nappinfos, + AppendRelInfo **appinfos); +static List *adjust_inherited_tlist(List *tlist, + AppendRelInfo *context); + +AppendRelInfo * +make_append_rel_info(RelOptInfo *parentrel, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex) +{ + AppendRelInfo *appinfo = makeNode(AppendRelInfo); + + appinfo->parent_relid = parentrel->relid; + appinfo->child_relid = childRTindex; + appinfo->parent_reltype = parentrel->reltype; + appinfo->child_reltype = childreltype; + make_inh_translation_list(parentrel->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, + &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; + + return appinfo; +} + +/* + * adjust_appendrel_attrs + * Copy the specified query or expression and translate Vars referring to a + * parent rel to refer to the corresponding child rel instead. We also + * update rtindexes appearing outside Vars, such as resultRelation and + * jointree relids. + * + * Note: this is only applied after conversion of sublinks to subplans, + * so we don't need to cope with recursion into sub-queries. + * + * Note: this is not hugely different from what pullup_replace_vars() does; + * maybe we should try to fold the two routines together. + */ +Node * +adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, + AppendRelInfo **appinfos) +{ + Node *result; + adjust_appendrel_attrs_context context; + + context.root = root; + context.nappinfos = nappinfos; + context.appinfos = appinfos; + + /* If there's nothing to adjust, don't call this function. */ + Assert(nappinfos >= 1 && appinfos != NULL); + + /* + * Must be prepared to start with a Query or a bare expression tree. + */ + if (node && IsA(node, Query)) + { + Query *newnode; + int cnt; + + newnode = query_tree_mutator((Query *) node, + adjust_appendrel_attrs_mutator, + (void *) &context, + QTW_IGNORE_RC_SUBQUERIES); + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (newnode->resultRelation == appinfo->parent_relid) + { + newnode->resultRelation = appinfo->child_relid; + /* Fix tlist resnos too, if it's inherited UPDATE */ + if (newnode->commandType == CMD_UPDATE) + newnode->targetList = + adjust_inherited_tlist(newnode->targetList, + appinfo); + break; + } + } + + result = (Node *) newnode; + } + else + result = adjust_appendrel_attrs_mutator(node, &context); + + return result; +} + +static Node * +adjust_appendrel_attrs_mutator(Node *node, + adjust_appendrel_attrs_context *context) +{ + AppendRelInfo **appinfos = context->appinfos; + int nappinfos = context->nappinfos; + int cnt; + + if (node == NULL) + return NULL; + if (IsA(node, Var)) + { + Var *var = (Var *) copyObject(node); + AppendRelInfo *appinfo = NULL; + + for (cnt = 0; cnt < nappinfos; cnt++) + { + if (var->varno == appinfos[cnt]->parent_relid) + { + appinfo = appinfos[cnt]; + break; + } + } + + if (var->varlevelsup == 0 && appinfo) + { + var->varno = appinfo->child_relid; + var->varnoold = appinfo->child_relid; + if (var->varattno > 0) + { + Node *newnode; + + if (var->varattno > list_length(appinfo->translated_vars)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + var->varattno, get_rel_name(appinfo->parent_reloid)); + newnode = copyObject(list_nth(appinfo->translated_vars, + var->varattno - 1)); + if (newnode == NULL) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + var->varattno, get_rel_name(appinfo->parent_reloid)); + return newnode; + } + else if (var->varattno == 0) + { + /* + * Whole-row Var: if we are dealing with named rowtypes, we + * can use a whole-row Var for the child table plus a coercion + * step to convert the tuple layout to the parent's rowtype. + * Otherwise we have to generate a RowExpr. + */ + if (OidIsValid(appinfo->child_reltype)) + { + Assert(var->vartype == appinfo->parent_reltype); + if (appinfo->parent_reltype != appinfo->child_reltype) + { + ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr); + + r->arg = (Expr *) var; + r->resulttype = appinfo->parent_reltype; + r->convertformat = COERCE_IMPLICIT_CAST; + r->location = -1; + /* Make sure the Var node has the right type ID, too */ + var->vartype = appinfo->child_reltype; + return (Node *) r; + } + } + else + { + /* + * Build a RowExpr containing the translated variables. + * + * In practice var->vartype will always be RECORDOID here, + * so we need to come up with some suitable column names. + * We use the parent RTE's column names. + * + * Note: we can't get here for inheritance cases, so there + * is no need to worry that translated_vars might contain + * some dummy NULLs. + */ + RowExpr *rowexpr; + List *fields; + RangeTblEntry *rte; + + rte = rt_fetch(appinfo->parent_relid, + context->root->parse->rtable); + fields = copyObject(appinfo->translated_vars); + rowexpr = makeNode(RowExpr); + rowexpr->args = fields; + rowexpr->row_typeid = var->vartype; + rowexpr->row_format = COERCE_IMPLICIT_CAST; + rowexpr->colnames = copyObject(rte->eref->colnames); + rowexpr->location = -1; + + return (Node *) rowexpr; + } + } + /* system attributes don't need any other translation */ + } + return (Node *) var; + } + if (IsA(node, CurrentOfExpr)) + { + CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node); + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (cexpr->cvarno == appinfo->parent_relid) + { + cexpr->cvarno = appinfo->child_relid; + break; + } + } + return (Node *) cexpr; + } + if (IsA(node, RangeTblRef)) + { + RangeTblRef *rtr = (RangeTblRef *) copyObject(node); + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (rtr->rtindex == appinfo->parent_relid) + { + rtr->rtindex = appinfo->child_relid; + break; + } + } + return (Node *) rtr; + } + if (IsA(node, JoinExpr)) + { + /* Copy the JoinExpr node with correct mutation of subnodes */ + JoinExpr *j; + AppendRelInfo *appinfo; + + j = (JoinExpr *) expression_tree_mutator(node, + adjust_appendrel_attrs_mutator, + (void *) context); + /* now fix JoinExpr's rtindex (probably never happens) */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + appinfo = appinfos[cnt]; + + if (j->rtindex == appinfo->parent_relid) + { + j->rtindex = appinfo->child_relid; + break; + } + } + return (Node *) j; + } + if (IsA(node, PlaceHolderVar)) + { + /* Copy the PlaceHolderVar node with correct mutation of subnodes */ + PlaceHolderVar *phv; + + phv = (PlaceHolderVar *) expression_tree_mutator(node, + adjust_appendrel_attrs_mutator, + (void *) context); + /* now fix PlaceHolderVar's relid sets */ + if (phv->phlevelsup == 0) + phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos, + context->appinfos); + return (Node *) phv; + } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + + /* Shouldn't need to handle planner auxiliary nodes here */ + Assert(!IsA(node, AppendRelInfo)); + Assert(!IsA(node, PlaceHolderInfo)); + Assert(!IsA(node, MinMaxAggInfo)); + + /* + * We have to process RestrictInfo nodes specially. (Note: although + * set_append_rel_pathlist will hide RestrictInfos in the parent's + * baserestrictinfo list from us, it doesn't hide those in joininfo.) + */ + if (IsA(node, RestrictInfo)) + { + RestrictInfo *oldinfo = (RestrictInfo *) node; + RestrictInfo *newinfo = makeNode(RestrictInfo); + + /* Copy all flat-copiable fields */ + memcpy(newinfo, oldinfo, sizeof(RestrictInfo)); + + /* Recursively fix the clause itself */ + newinfo->clause = (Expr *) + adjust_appendrel_attrs_mutator((Node *) oldinfo->clause, context); + + /* and the modified version, if an OR clause */ + newinfo->orclause = (Expr *) + adjust_appendrel_attrs_mutator((Node *) oldinfo->orclause, context); + + /* adjust relid sets too */ + newinfo->clause_relids = adjust_child_relids(oldinfo->clause_relids, + context->nappinfos, + context->appinfos); + newinfo->required_relids = adjust_child_relids(oldinfo->required_relids, + context->nappinfos, + context->appinfos); + newinfo->outer_relids = adjust_child_relids(oldinfo->outer_relids, + context->nappinfos, + context->appinfos); + newinfo->nullable_relids = adjust_child_relids(oldinfo->nullable_relids, + context->nappinfos, + context->appinfos); + newinfo->left_relids = adjust_child_relids(oldinfo->left_relids, + context->nappinfos, + context->appinfos); + newinfo->right_relids = adjust_child_relids(oldinfo->right_relids, + context->nappinfos, + context->appinfos); + + /* + * Reset cached derivative fields, since these might need to have + * different values when considering the child relation. Note we + * don't reset left_ec/right_ec: each child variable is implicitly + * equivalent to its parent, so still a member of the same EC if any. + */ + newinfo->eval_cost.startup = -1; + newinfo->norm_selec = -1; + newinfo->outer_selec = -1; + newinfo->left_em = NULL; + newinfo->right_em = NULL; + newinfo->scansel_cache = NIL; + newinfo->left_bucketsize = -1; + newinfo->right_bucketsize = -1; + newinfo->left_mcvfreq = -1; + newinfo->right_mcvfreq = -1; + + return (Node *) newinfo; + } + + /* + * NOTE: we do not need to recurse into sublinks, because they should + * already have been converted to subplans before we see them. + */ + Assert(!IsA(node, SubLink)); + Assert(!IsA(node, Query)); + + return expression_tree_mutator(node, adjust_appendrel_attrs_mutator, + (void *) context); +} + +/* + * Substitute child relids for parent relids in a Relid set. The array of + * appinfos specifies the substitutions to be performed. + */ +static Relids +adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos) +{ + Bitmapset *result = NULL; + int cnt; + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + /* Remove parent, add child */ + if (bms_is_member(appinfo->parent_relid, relids)) + { + /* Make a copy if we are changing the set. */ + if (!result) + result = bms_copy(relids); + + result = bms_del_member(result, appinfo->parent_relid); + result = bms_add_member(result, appinfo->child_relid); + } + } + + /* If we made any changes, return the modified copy. */ + if (result) + return result; + + /* Otherwise, return the original set without modification. */ + return relids; +} + +/* + * Replace any relid present in top_parent_relids with its child in + * child_relids. Members of child_relids can be multiple levels below top + * parent in the partition hierarchy. + */ +Relids +adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, + Relids child_relids, Relids top_parent_relids) +{ + AppendRelInfo **appinfos; + int nappinfos; + Relids parent_relids = NULL; + Relids result; + Relids tmp_result = NULL; + int cnt; + + /* + * If the given relids set doesn't contain any of the top parent relids, + * it will remain unchanged. + */ + if (!bms_overlap(relids, top_parent_relids)) + return relids; + + appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); + + /* Construct relids set for the immediate parent of the given child. */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); + } + + /* Recurse if immediate parent is not the top parent. */ + if (!bms_equal(parent_relids, top_parent_relids)) + { + tmp_result = adjust_child_relids_multilevel(root, relids, + parent_relids, + top_parent_relids); + relids = tmp_result; + } + + result = adjust_child_relids(relids, nappinfos, appinfos); + + /* Free memory consumed by any intermediate result. */ + if (tmp_result) + bms_free(tmp_result); + bms_free(parent_relids); + pfree(appinfos); + + return result; +} + +/* + * adjust_appendrel_attrs_multilevel + * Apply Var translations from a toplevel appendrel parent down to a child. + * + * In some cases we need to translate expressions referencing a parent relation + * to reference an appendrel child that's multiple levels removed from it. + */ +Node * +adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, + Relids child_relids, + Relids top_parent_relids) +{ + AppendRelInfo **appinfos; + Bitmapset *parent_relids = NULL; + int nappinfos; + int cnt; + + Assert(bms_num_members(child_relids) == bms_num_members(top_parent_relids)); + + appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); + + /* Construct relids set for the immediate parent of given child. */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); + } + + /* Recurse if immediate parent is not the top parent. */ + if (!bms_equal(parent_relids, top_parent_relids)) + node = adjust_appendrel_attrs_multilevel(root, node, parent_relids, + top_parent_relids); + + /* Now translate for this child */ + node = adjust_appendrel_attrs(root, node, nappinfos, appinfos); + + pfree(appinfos); + + return node; +} + +/* + * Construct the SpecialJoinInfo for a child-join by translating + * SpecialJoinInfo for the join between parents. left_relids and right_relids + * are the relids of left and right side of the join respectively. + */ +SpecialJoinInfo * +build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, + Relids left_relids, Relids right_relids) +{ + SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo); + AppendRelInfo **left_appinfos; + int left_nappinfos; + AppendRelInfo **right_appinfos; + int right_nappinfos; + + memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo)); + left_appinfos = find_appinfos_by_relids(root, left_relids, + &left_nappinfos); + right_appinfos = find_appinfos_by_relids(root, right_relids, + &right_nappinfos); + + sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand, + left_nappinfos, left_appinfos); + sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand, + right_nappinfos, + right_appinfos); + sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand, + left_nappinfos, left_appinfos); + sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand, + right_nappinfos, + right_appinfos); + sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root, + (Node *) sjinfo->semi_rhs_exprs, + right_nappinfos, + right_appinfos); + + pfree(left_appinfos); + pfree(right_appinfos); + + return sjinfo; +} + +/* + * find_appinfos_by_relids + * Find AppendRelInfo structures for all relations specified by relids. + * + * The AppendRelInfos are returned in an array, which can be pfree'd by the + * caller. *nappinfos is set to the number of entries in the array. + */ +AppendRelInfo ** +find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos) +{ + AppendRelInfo **appinfos; + int cnt = 0; + int i; + + *nappinfos = bms_num_members(relids); + appinfos = (AppendRelInfo **) palloc(sizeof(AppendRelInfo *) * *nappinfos); + + i = -1; + while ((i = bms_next_member(relids, i)) >= 0) + { + AppendRelInfo *appinfo = root->append_rel_array[i]; + + if (!appinfo) + elog(ERROR, "child rel %d not found in append_rel_array", i); + + appinfos[cnt++] = appinfo; + } + return appinfos; +} + +/* + * make_inh_translation_list + * Build the list of translations from parent Vars to child Vars for + * an inheritance child. + * + * For paranoia's sake, we match type/collation as well as attribute name. + */ +static void +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) +{ + List *vars = NIL; + int oldnatts = old_tupdesc->natts; + int newnatts = new_tupdesc->natts; + int old_attno; + int new_attno = 0; + + for (old_attno = 0; old_attno < oldnatts; old_attno++) + { + Form_pg_attribute att; + char *attname; + Oid atttypid; + int32 atttypmod; + Oid attcollation; + + att = TupleDescAttr(old_tupdesc, old_attno); + if (att->attisdropped) + { + /* Just put NULL into this list entry */ + vars = lappend(vars, NULL); + continue; + } + attname = NameStr(att->attname); + atttypid = att->atttypid; + atttypmod = att->atttypmod; + attcollation = att->attcollation; + + /* + * When we are generating the "translation list" for the parent table + * of an inheritance set, no need to search for matches. + */ + if (from_rel == to_rel) + { + vars = lappend(vars, makeVar(newvarno, + (AttrNumber) (old_attno + 1), + atttypid, + atttypmod, + attcollation, + 0)); + continue; + } + + /* + * Otherwise we have to search for the matching column by name. + * There's no guarantee it'll have the same column position, because + * of cases like ALTER TABLE ADD COLUMN and multiple inheritance. + * However, in simple cases, the relative order of columns is mostly + * the same in both relations, so try the column of newrelation that + * follows immediately after the one that we just found, and if that + * fails, let syscache handle it. + */ + if (new_attno >= newnatts || + (att = TupleDescAttr(new_tupdesc, new_attno))->attisdropped || + strcmp(attname, NameStr(att->attname)) != 0) + { + HeapTuple newtup; + + newtup = SearchSysCacheAttName(to_rel, attname); + if (!newtup) + elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", + attname, get_rel_name(to_rel)); + new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; + ReleaseSysCache(newtup); + + att = TupleDescAttr(new_tupdesc, new_attno); + } + + /* Found it, check type and collation match */ + if (atttypid != att->atttypid || atttypmod != att->atttypmod) + elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", + attname, get_rel_name(to_rel)); + if (attcollation != att->attcollation) + elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", + attname, get_rel_name(to_rel)); + + vars = lappend(vars, makeVar(newvarno, + (AttrNumber) (new_attno + 1), + atttypid, + atttypmod, + attcollation, + 0)); + new_attno++; + } + + *translated_vars = vars; +} + +/* + * translate_col_privs + * Translate a bitmapset representing per-column privileges from the + * parent rel's attribute numbering to the child's. + * + * The only surprise here is that we don't translate a parent whole-row + * reference into a child whole-row reference. That would mean requiring + * permissions on all child columns, which is overly strict, since the + * query is really only going to reference the inherited columns. Instead + * we set the per-column bits for all inherited columns. + */ +Bitmapset * +translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars) +{ + Bitmapset *child_privs = NULL; + bool whole_row; + int attno; + ListCell *lc; + + /* System attributes have the same numbers in all tables */ + for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) + { + if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + attno - FirstLowInvalidHeapAttributeNumber); + } + + /* Check if parent has whole-row reference */ + whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, + parent_privs); + + /* And now translate the regular user attributes, using the vars list */ + attno = InvalidAttrNumber; + foreach(lc, translated_vars) + { + Var *var = lfirst_node(Var, lc); + + attno++; + if (var == NULL) /* ignore dropped columns */ + continue; + if (whole_row || + bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + var->varattno - FirstLowInvalidHeapAttributeNumber); + } + + return child_privs; +} + +/* + * Adjust the targetlist entries of an inherited UPDATE operation + * + * The expressions have already been fixed, but we have to make sure that + * the target resnos match the child table (they may not, in the case of + * a column that was added after-the-fact by ALTER TABLE). In some cases + * this can force us to re-order the tlist to preserve resno ordering. + * (We do all this work in special cases so that preptlist.c is fast for + * the typical case.) + * + * The given tlist has already been through expression_tree_mutator; + * therefore the TargetEntry nodes are fresh copies that it's okay to + * scribble on. + * + * Note that this is not needed for INSERT because INSERT isn't inheritable. + */ +static List * +adjust_inherited_tlist(List *tlist, AppendRelInfo *context) +{ + bool changed_it = false; + ListCell *tl; + List *new_tlist; + bool more; + int attrno; + + /* This should only happen for an inheritance case, not UNION ALL */ + Assert(OidIsValid(context->parent_reloid)); + + /* Scan tlist and update resnos to match attnums of child rel */ + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + Var *childvar; + + if (tle->resjunk) + continue; /* ignore junk items */ + + /* Look up the translation of this column: it must be a Var */ + if (tle->resno <= 0 || + tle->resno > list_length(context->translated_vars)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + tle->resno, get_rel_name(context->parent_reloid)); + childvar = (Var *) list_nth(context->translated_vars, tle->resno - 1); + if (childvar == NULL || !IsA(childvar, Var)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + tle->resno, get_rel_name(context->parent_reloid)); + + if (tle->resno != childvar->varattno) + { + tle->resno = childvar->varattno; + changed_it = true; + } + } + + /* + * If we changed anything, re-sort the tlist by resno, and make sure + * resjunk entries have resnos above the last real resno. The sort + * algorithm is a bit stupid, but for such a seldom-taken path, small is + * probably better than fast. + */ + if (!changed_it) + return tlist; + + new_tlist = NIL; + more = true; + for (attrno = 1; more; attrno++) + { + more = false; + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + + if (tle->resjunk) + continue; /* ignore junk items */ + + if (tle->resno == attrno) + new_tlist = lappend(new_tlist, tle); + else if (tle->resno > attrno) + more = true; + } + } + + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + + if (!tle->resjunk) + continue; /* here, ignore non-junk items */ + + tle->resno = attrno; + new_tlist = lappend(new_tlist, tle); + attrno++; + } + + return new_tlist; +} diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index d50d86b252..8ce88876c4 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -20,6 +20,7 @@ #include "foreign/fdwapi.h" #include "nodes/extensible.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index c23db9d78d..52a11f434f 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -17,6 +17,7 @@ #include <limits.h> #include "miscadmin.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index fcf8d6032c..e76906da1f 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -44,6 +44,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/include/optimizer/append.h b/src/include/optimizer/append.h new file mode 100644 index 0000000000..b5f025c137 --- /dev/null +++ b/src/include/optimizer/append.h @@ -0,0 +1,25 @@ +/*------------------------------------------------------------------------- + * + * append.h + * prototypes for append.c. + * + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/optimizer/append.h + * + *------------------------------------------------------------------------- + */ +#ifndef APPEND_H +#define APPEND_H + +#include "nodes/relation.h" + +/* + * append.c + * utilities for dealing with append relations + */ +extern void expand_inherited_tables(PlannerInfo *root); + +#endif /* APPEND_H */ diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h new file mode 100644 index 0000000000..e205e78e6d --- /dev/null +++ b/src/include/optimizer/appendinfo.h @@ -0,0 +1,43 @@ +/*------------------------------------------------------------------------- + * + * appendinfo.h + * Routines for mapping expressions between append rel parent(s) and + * children + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/optimizer/appendinfo.h + * + *------------------------------------------------------------------------- + */ +#ifndef APPENDINFO_H +#define APPENDINFO_H + +#include "nodes/plannodes.h" +#include "nodes/relation.h" +#include "utils/relcache.h" + +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parentrel, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex); +extern Bitmapset *translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars); +extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, + int nappinfos, AppendRelInfo **appinfos); + +extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, + Relids child_relids, + Relids top_parent_relids); + +extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root, + Relids relids, int *nappinfos); + +extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, + SpecialJoinInfo *parent_sjinfo, + Relids left_relids, Relids right_relids); +extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, + Relids child_relids, Relids top_parent_relids); + +#endif /* APPENDINFO_H */ diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index faae07d240..3ad9b4a77e 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -48,24 +48,5 @@ extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); */ extern RelOptInfo *plan_set_operations(PlannerInfo *root); -extern void expand_inherited_tables(PlannerInfo *root); -extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, - int nappinfos, AppendRelInfo **appinfos); -extern AppendRelInfo *make_append_rel_info(RelOptInfo *parentrel, - RangeTblEntry *parentrte, - TupleDesc childdesc, Oid childoid, Oid childreltype, - Index childRTindex); -extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, - Relids child_relids, - Relids top_parent_relids); - -extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root, - Relids relids, int *nappinfos); - -extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, - SpecialJoinInfo *parent_sjinfo, - Relids left_relids, Relids right_relids); -extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, - Relids child_relids, Relids top_parent_relids); #endif /* PREP_H */ -- 2.11.0
v6-0005-Teach-planner-to-only-process-unpruned-partitions.patch (text/plain, 7.2K) download From 12797690b709a797b9fdaa5152a875416ab08f96 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 15 Oct 2018 10:59:24 +0900 Subject: [PATCH v6 5/6] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/append.c | 8 ++++++++ src/backend/optimizer/util/relnode.c | 3 +++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/relation.h | 2 ++ 6 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 6e321ec9e7..76531a05cf 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1424,6 +1424,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index a222bd8f1b..14e845f5d0 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6765,7 +6765,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -6773,9 +6775,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -6856,7 +6856,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -6868,7 +6867,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -6878,9 +6879,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/append.c b/src/backend/optimizer/util/append.c index d2ad80034f..3b3fb304b9 100644 --- a/src/backend/optimizer/util/append.c +++ b/src/backend/optimizer/util/append.c @@ -351,6 +351,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, /* Perform pruning. */ partindexes = prune_append_rel_partitions(parentrel); + parentrel->live_parts = partindexes; /* Must expand PlannerInfo arrays before we can add children. */ expand_planner_arrays(root, bms_num_members(partindexes)); @@ -394,6 +395,13 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, Assert(childrel != NULL); parentrel->part_rels[i] = childrel; + /* + * If partition is excluded by constraints, remove it from + * live_parts, too. + */ + if (IS_DUMMY_REL(childrel)) + parentrel->live_parts = bms_del_member(parentrel->live_parts, i); + /* Close child relations, but keep locks */ heap_close(newrelation, NoLock); diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 52a11f434f..6c7eafe6db 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1748,6 +1748,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index e76906da1f..8bc2f5c5c2 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -441,29 +441,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index e6fdbcd030..f43fa46fc5 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -727,6 +727,8 @@ typedef struct RelOptInfo Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Set of live partitions; contains indexes + * into part_rels array */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.11.0
v6-0006-Do-not-lock-all-partitions-at-the-beginning.patch (text/plain, 1.2K) download From d4387591743853cdaa6a21d17a3560a8e0d671d6 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 12 Oct 2018 19:12:53 +0900 Subject: [PATCH v6 6/6] Do not lock all partitions at the beginning --- src/backend/optimizer/util/append.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/backend/optimizer/util/append.c b/src/backend/optimizer/util/append.c index 3b3fb304b9..0b0b75d409 100644 --- a/src/backend/optimizer/util/append.c +++ b/src/backend/optimizer/util/append.c @@ -335,10 +335,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, parentrel->inh_root_parent : parentRTindex; - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - /* * Initialize partitioned_child_rels to contain this RT index. * @@ -374,8 +370,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, RangeTblEntry *childrte; Index childRTindex; - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); + newrelation = heap_open(childOID, lockmode); Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-11-14 11:01  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 1 reply; 265+ messages in thread

From: Amit Langote @ 2018-11-14 11:01 UTC (permalink / raw)
  To: David Rowley <david.rowley@2ndquadrant.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers

On 2018/11/14 19:28, Amit Langote wrote:
> Attached updated patches.  Significant revisions are as follows (note that
> I reversed the order of 0001 and 0002):
> 
> v6-0001-Overhaul-inheritance-update-delete-planning.patch
> 
> The major changes is fixing the problem above.
> 
> v6-0002-Store-inheritance-root-parent-index-in-otherrel-s.patch
> 
> No change.
> 
> v6-0003-Lazy-creation-of-RTEs-for-inheritance-children.patch
> 
> Made inheritance expansion a separate step of make_one_rel whereas
> previously it would be invoked at the beginning of set_append_rel_size.
> Now, it runs just before set_base_rel_sizes.  The same step also
> recursively expands (and performs pruning for) any child partitioned
> tables that were added by the expansion of partitioned tables originally
> mentioned in the query.  With this change, we don't need to worry about
> the range table changing as set_base_rel_size is executing, which could
> lead to problems.
> 
> v6-0004-Move-append-expansion-code-into-its-own-file.patch
> v6-0005-Teach-planner-to-only-process-unpruned-partitions.patch
> v6-0006-Do-not-lock-all-partitions-at-the-beginning.patch

This went out sooner than it should have.  I hadn't waited for make
check-world to finish which showed that a file_fdw test exercising
inheritance crashed with 0001 patch due to a bogus Assert.

Fixed it in the attached version.

Thanks,
Amit

Attachments: download all (6 files, .tar.gz)

v7-0001-Overhaul-inheritance-update-delete-planning.patch (text/plain, 51.5K) download From 52a66aca3b1eabdba4a151590a14b7fa013eb9f5 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 11:18:12 +0900 Subject: [PATCH v7 1/6] Overhaul inheritance update/delete planning With the current method, inheritance_planner, which handles the planning for update/delete commands targeting inheritance trees, performs the *whole* planning for each child table. That involves translating the query tree to set a given child table in place of the original inheritance root table as the query's target table, followed by calling grouping_planner on the translated query, which performs query planning and additional steps needed to apply correct target list based on the child target table. That's inefficient for two reasons: 1. partprune.c cannot be used to perform partition pruning, because it can only be invoked from query_planner. With the current approach, query_planner only sees the individual partitions in the query tree it receives, not the parent partitioned table. This leaves each partition to be pruned using constraint exclusion. (constraint exclusion cannon prune hash partitions which means UPDATE/DELETE don't support hash partition pruning.) 2. Repeated invocation of query_planner results in allocating large amounts of memory, especially if there are many child tables. Also, it involves repeatedly performing the same processing, such as jointree processing. This commit addresses both of the issues by rewriting portions of inheritance_planner and adding some supporting functionality to allpath.c. With the new implementation, inheritance_planner calls query_planner only once at the beginning with the original unmodified query, which creates the access paths of individual relations after pruning any unnecessary partitions. For each target child relation, it also creates a sub-PartitionInfo containing translated version of the query and a targetlist suitable for the child. Child PlannerInfos are saved in the top PlannerInfo for using later. If the query involves join against the target relation, join paths are created for each target child relation by replacing the original target table in the join tree by a given child table. Join relations (RelOptInfos thereof) for all target child relations are collected in a global list in the top PlannerInfo. After creating the join paths for all target child relations, inheritance_planner calls grouping_planner() on each child join relation using the previously created child PlannerInfo to finish up the paths such that they produce query's top-level target list expanded according to a given child relation's descriptor. grouping_planner()'s interface is modified so that we can pass it what's called a 'planned_rel', a RelOptInfo that already contains the necessary paths needed to produce its output. This removes some existing code in inheritance_planner that dealt with any subquery RTEs in the query. The rationale of that code was that the subquery RTEs may change during each iteration of planning (that is, for different children), so different iterations better use different copies of those RTEs. That was handled by making fresh copies of those RTEs for each iteration of planning which were appended to the range table, accompanied by modifying all expressions and auxiliary structures that referenced the original subquery RTEs to instead reference the copies (that is, change the varnos). This copying meant we would end up adding S * N new entries to the original range table by the time we got to the last unpruned child, where S is the number of subquery RTEs in the original query and N the number of unpruned children. Since with the new code we perform planning just once, I think we don't need this special handling. Actually, there is a regression test output change due to no longer having copies of subquery RTEs (see the diff of partition_join.out file.) Also we no longer need the hack in relation_excluded_by_constraints() involving setting PlannerInfo.inhTargetKind to enable constraint exclusion for target child relation, which is no longer is no longer needed. Constraint exclusion runs during query_planner step described above. Regression test output change in partition_join.out is due to the fact that we no longer create duplicates of subquery RTEs in child range tables. --- doc/src/sgml/ddl.sgml | 14 +- src/backend/nodes/outfuncs.c | 1 - src/backend/optimizer/path/allpaths.c | 307 ++++++++++++++++++- src/backend/optimizer/plan/createplan.c | 10 - src/backend/optimizer/plan/planner.c | 428 +++++++-------------------- src/backend/optimizer/prep/prepjointree.c | 1 - src/backend/optimizer/prep/prepunion.c | 33 ++- src/backend/optimizer/util/plancat.c | 50 +--- src/include/nodes/relation.h | 31 +- src/test/regress/expected/partition_join.out | 4 +- 10 files changed, 472 insertions(+), 407 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index c8268222af..ca7359c4a4 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -3935,15 +3935,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <note> <para> - Currently, pruning of partitions during the planning of an - <command>UPDATE</command> or <command>DELETE</command> command is - implemented using the constraint exclusion method (however, it is - controlled by the <literal>enable_partition_pruning</literal> rather than - <literal>constraint_exclusion</literal>) &mdash; see the following section - for details and caveats that apply. - </para> - - <para> Execution-time partition pruning currently only occurs for the <literal>Append</literal> and <literal>MergeAppend</literal> node types. It is not yet implemented for the <literal>ModifyTable</literal> node @@ -3966,9 +3957,8 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <para> <firstterm>Constraint exclusion</firstterm> is a query optimization - technique similar to partition pruning. While it is primarily used - for partitioning implemented using the legacy inheritance method, it can be - used for other purposes, including with declarative partitioning. + technique similar to partition pruning. It is primarily used + for partitioning implemented using the legacy inheritance method. </para> <para> diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index f0c396530d..08657fcfd9 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2309,7 +2309,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); WRITE_UINT_FIELD(qual_security_level); - WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind); WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasDeletedRTEs); diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 738bb30848..7787592870 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -36,6 +36,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" @@ -93,6 +94,9 @@ static void set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); +static PlannerInfo *adjust_inherit_target_child(PlannerInfo *root, + RelOptInfo *childrel, + AppendRelInfo *appinfo); static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, @@ -119,6 +123,8 @@ static void set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); +static void inheritance_make_rel_from_joinlist(PlannerInfo *root, + List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, pushdown_safety_info *safetyInfo); static bool recurse_pushdown_safe(Node *setOp, Query *topquery, @@ -217,13 +223,40 @@ make_one_rel(PlannerInfo *root, List *joinlist) /* * Generate access paths for the entire join tree. + * + * For UPDATE/DELETE on an inheritance parent, join paths should be + * generated for each child result rel separately. */ - rel = make_rel_from_joinlist(root, joinlist); + if (root->parse->resultRelation && + root->simple_rte_array[root->parse->resultRelation]->inh) + { + inheritance_make_rel_from_joinlist(root, joinlist); - /* - * The result should join all and only the query's base rels. - */ - Assert(bms_equal(rel->relids, root->all_baserels)); + /* + * There should be as many child source rels as there are child + * subroots. + */ + Assert(list_length(root->inh_target_child_roots) == + list_length(root->inh_target_child_rels)); + + /* + * Return the RelOptInfo of original target relation, although this + * doesn't really contain the final path. inheritance_planner + * from where we got here will generate the final path, but not + * through this RelOptInfo. + */ + rel = find_base_rel(root, root->parse->resultRelation); + } + else + { + rel = make_rel_from_joinlist(root, joinlist); + + /* + * The result should join all and only the query's base rels. + */ + Assert(bms_equal(rel->relids, root->all_baserels)); + + } return rel; } @@ -1001,6 +1034,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, ListCell *parentvars; ListCell *childvars; ListCell *lc; + PlannerInfo *subroot = root; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1219,9 +1253,17 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, set_rel_consider_parallel(root, childrel, childRTE); /* - * Compute the child's size. + * If the parent is the result relation, we need a reltarget for the + * child relation that will be suitable to use the child also as the + * target relation. */ - set_rel_size(root, childrel, childRTindex, childRTE); + if (appinfo->parent_relid == root->parse->resultRelation) + subroot = adjust_inherit_target_child(root, childrel, appinfo); + + /* + * Compute the child's size using possibly modified subroot. + */ + set_rel_size(subroot, childrel, childRTindex, childRTE); /* * It is possible that constraint exclusion detected a contradiction @@ -1231,6 +1273,26 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, if (IS_DUMMY_REL(childrel)) continue; + /* + * If we modified subroot for the target inheritance case, add it + * to root->inh_target_child_roots. + */ + if (subroot != root) + { + root->inh_target_child_roots = + lappend(root->inh_target_child_roots, subroot); + + /* + * If the childrel itself was a partitioned table, its children + * would've been added into subroot. Copy relevant fields + * into the parent root. + */ + if (subroot->inh_target_child_roots != NIL) + root->inh_target_child_roots = + list_concat(root->inh_target_child_roots, + subroot->inh_target_child_roots); + } + /* We have at least one live child. */ has_live_children = true; @@ -1327,6 +1389,109 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, } /* + * adjust_inherit_target_child + * For an inheritance target child relation, this adjusts its + * reltarget so that it contains expressions suitable for processing as + * a target relation, creates a child PlannerInfo containing translated + * copy of the query and returns it. + * + * The child PlannerInfo reuses most of the parent PlannerInfo's fields + * unchanged, except unexpanded_tlist and processed_tlist are based on the + * child relation. + */ +static PlannerInfo * +adjust_inherit_target_child(PlannerInfo *root, RelOptInfo *childrel, + AppendRelInfo *appinfo) +{ + PlannerInfo *subroot; + List *tlist; + List *translated_exprs; + ListCell *lc2; + + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + + /* + * We'd like to build the reltarget afresh; save the translated + * version of parent's expressions aside. + */ + translated_exprs = childrel->reltarget->exprs; + childrel->reltarget->exprs = NIL; + + /* Translate the original query's expressions to this child. */ + subroot = makeNode(PlannerInfo); + memcpy(subroot, root, sizeof(PlannerInfo)); + + /* + * Restore the unexpanded tlist for translation, so that child's + * query contains targetList numbered (resnos) per its own + * TupleDesc, which adjust_inherited_tlist ensures. + */ + root->parse->targetList = root->unexpanded_tlist; + subroot->parse = (Query *) adjust_appendrel_attrs(root, + (Node *) root->parse, + 1, &appinfo); + + /* + * Save subroot's targetlist so that childrel's own children can use it as + * unexpanded tlist. Must copy because subroot->parse->targetList will + * be modified soon. + */ + subroot->unexpanded_tlist = list_copy(subroot->parse->targetList); + + /* + * Apply planner's expansion of targetlist, such as adding various junk + * column, filling placeholder entries for dropped columns, etc., all of + * which occurs with the child's TupleDesc. + */ + tlist = preprocess_targetlist(subroot); + subroot->processed_tlist = tlist; + build_base_rel_tlists(subroot, tlist); + + /* + * Some of the expressions in parent's reltarget might not be in the + * child's freshly built reltarget expressions, because the latter only + * contains those attributes that are needed to be present in the top- + * level tlist (or ones that preprocess_targetlist thinks are needed to + * be in the tlist.) We may need other attributes such as those that + * are required for computing WHERE clauses, which are already computed + * for the parent during deconstruct_jointree processing of the original + * query. We've already got a translated copy of those attributes, from + * which pick only those that are not already present. + */ + foreach(lc2, translated_exprs) + { + Expr *expr = lfirst(lc2); + + if (!list_member(childrel->reltarget->exprs, expr)) + childrel->reltarget->exprs = lappend(childrel->reltarget->exprs, + expr); + } + + /* + * Set a few other fields of subroot. + * + * Reset inh_target_child_roots to not be same as parent root's so that + * the subroots for this child's own children (if any) don't end up in + * root parent's list. We'll eventually merge all entries into one list, + * but that's now now. + */ + subroot->inh_target_child_roots = NIL; + + /* + * Adjust all_baserels to replace the original target relation with the + * child target relation. Copy it before modifying though. + */ + subroot->all_baserels = bms_copy(root->all_baserels); + subroot->all_baserels = bms_del_member(subroot->all_baserels, + root->parse->resultRelation); + subroot->all_baserels = bms_add_member(subroot->all_baserels, + subroot->parse->resultRelation); + + return subroot; +} + +/* * set_append_rel_pathlist * Build access paths for an "append relation" */ @@ -2624,6 +2789,134 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows) } /* + * inheritance_make_rel_from_joinlist + * Perform join planning for all non-dummy leaf inheritance children + * in their role as an UPDATE/DELETE query's target relation + * + * If a child relation is a partitioned table, its children are processed in + * turn by recursively calling this function. + */ +static void +inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; +#ifdef USE_ASSERT_CHECKING + Relids all_baserels; +#endif + + /* + * For UPDATE/DELETE queries, the top parent can only ever be a table. + * As a contrast, it could be a UNION ALL subquery in the case of SELECT. + */ + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + Assert(planner_rt_fetch(resultRelation, root)->rtekind == RTE_RELATION); + + /* Nothing to do. */ + if (IS_DUMMY_REL(find_base_rel(root, resultRelation ))) + return; + + foreach(lc, root->inh_target_child_roots) + { + PlannerInfo *subroot = lfirst(lc); + RelOptInfo *childrel; + AppendRelInfo *appinfo; + List *translated_joinlist; + + Assert(subroot->parse->resultRelation > 0); + childrel = find_base_rel(root, subroot->parse->resultRelation); + appinfo = root->append_rel_array[subroot->parse->resultRelation]; + + if (appinfo->parent_relid != root->parse->resultRelation) + continue; + + /* Ignore excluded/pruned children. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* + * Modify joinlist such that relations joined to the top parent rel + * appear to be joined to the child rel instead. Do the same for + * any SpecialJoinInfo structs. + */ + translated_joinlist = (List *) + adjust_appendrel_attrs(subroot, + (Node *) joinlist, + 1, &appinfo); + subroot->join_info_list = (List *) + adjust_appendrel_attrs(subroot, + (Node *) root->join_info_list, + 1, &appinfo); + + /* + * Sub-partitioned tables have to be processed recursively using the + * translated subroot as the parent, because AppendRelInfos link + * sub-partitions to their immediate parents, not the root partitioned + * table. + */ + if (childrel->part_scheme != NULL) + { + inheritance_make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * Add this child relation as a placeholder in the parent root's + * inh_target_child_rels so that inheritance_planner see same + * number of entries as inh_target_child_roots. + */ + root->inh_target_child_rels = + lappend(root->inh_target_child_rels, childrel); + + /* Also propagate this child's own children into parent's list. */ + if (subroot->inh_target_child_rels != NIL) + root->inh_target_child_rels = + list_concat(root->inh_target_child_rels, + subroot->inh_target_child_rels); + continue; + } + + /* + * Since we added the child rel directly into the join tree, we must + * modify it to be a "base" rel instead of an "other" rel, which the + * join planning code expects the relations being joined to be. + * + * NB: Do we need to change the child EC members to be marked + * as non-child somehow? + */ + childrel->reloptkind = RELOPT_BASEREL; + + Assert(subroot->join_rel_list == NIL); + Assert(subroot->join_rel_hash == NULL); + + /* Perform join planning and save the resulting RelOptInfo. */ + childrel = make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * Remember this child target rel. inheritance_planner will perform + * the remaining steps of planning for each child relation separately. + * Specifically, it will call grouping_planner on every + * RelOptInfo contained in the inh_target_child_rels list, each of + * which represents the source of tuples to be modified for a given + * target child rel. + */ + root->inh_target_child_rels = + lappend(root->inh_target_child_rels, childrel); +#ifdef USE_ASSERT_CHECKING + /* + * The following implements essentially the same Assert as in + * make_one_rel, our caller. + */ + all_baserels = bms_copy(root->all_baserels); + all_baserels = bms_del_member(all_baserels, + root->parse->resultRelation); + all_baserels = bms_add_member(all_baserels, + subroot->parse->resultRelation); + Assert(bms_equal(childrel->relids, all_baserels)); +#endif + } +} + +/* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. * diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index da7a92081a..28c4b53fea 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -1998,12 +1998,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) /* * During setrefs.c, we'll need the grouping_map to fix up the cols lists * in GroupingFunc nodes. Save it for setrefs.c to use. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be grouping in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->grouping_map == NULL); root->grouping_map = grouping_map; @@ -2160,12 +2155,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) * with InitPlan output params. (We can't just do that locally in the * MinMaxAgg node, because path nodes above here may have Agg references * as well.) Save the mmaggregates list to tell setrefs.c to do that. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be aggregates in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->minmax_aggs == NIL); root->minmax_aggs = best_path->mmaggregates; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index c729a99f8b..d094b3092b 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -37,6 +37,7 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "nodes/relation.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" @@ -126,7 +127,7 @@ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static void inheritance_planner(PlannerInfo *root); static void grouping_planner(PlannerInfo *root, bool inheritance_update, - double tuple_fraction); + RelOptInfo *planned_rel, double tuple_fraction); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, int *tleref_to_colnum_map); @@ -630,7 +631,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->grouping_map = NULL; root->minmax_aggs = NIL; root->qual_security_level = 0; - root->inhTargetKind = INHKIND_NONE; root->hasRecursion = hasRecursion; if (hasRecursion) root->wt_param_id = SS_assign_special_param(root); @@ -970,7 +970,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh) inheritance_planner(root); else - grouping_planner(root, false, tuple_fraction); + grouping_planner(root, false, NULL, tuple_fraction); /* * Capture the set of outer-level param IDs we have access to, for use in @@ -1146,12 +1146,19 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr) * inheritance set. * * We have to handle this case differently from cases where a source relation - * is an inheritance set. Source inheritance is expanded at the bottom of the - * plan tree (see allpaths.c), but target inheritance has to be expanded at - * the top. The reason is that for UPDATE, each target relation needs a - * different targetlist matching its own column set. Fortunately, - * the UPDATE/DELETE target can never be the nullable side of an outer join, - * so it's OK to generate the plan this way. + * is an inheritance set. That's mainly because for UPDATE, each target + * relation needs a different targetlist matching its own column set. So, we + * must modify the source scan/join path for each target relation such that it + * produces the desired target list. + * + * The source scan/join paths for individual target relations are still + * created in allpaths.c by first expanding the inheritance in the usual way + * by set_append_rel_size, followed by join planning for each target relation + * separately in make_one_rel. Finally, we apply grouping_planner here to each + * child scan/join path so that it produces the desired targetlist. + * + * Fortunately, the UPDATE/DELETE target can never be the nullable side of an + * outer join, so it's OK to generate the plan this way. * * Returns nothing; the useful output is in the Paths we attach to * the (UPPERREL_FINAL, NULL) upperrel stored in *root. @@ -1164,14 +1171,8 @@ inheritance_planner(PlannerInfo *root) { Query *parse = root->parse; int top_parentRTindex = parse->resultRelation; - Bitmapset *subqueryRTindexes; - Bitmapset *modifiableARIindexes; int nominalRelation = -1; Index rootRelation = 0; - List *final_rtable = NIL; - int save_rel_array_size = 0; - RelOptInfo **save_rel_array = NULL; - AppendRelInfo **save_append_rel_array = NULL; List *subpaths = NIL; List *subroots = NIL; List *resultRelations = NIL; @@ -1179,70 +1180,59 @@ inheritance_planner(PlannerInfo *root) List *returningLists = NIL; List *rowMarks; RelOptInfo *final_rel; - ListCell *lc; - Index rti; + ListCell *lc1, + *lc2; RangeTblEntry *parent_rte; - PlannerInfo *parent_root; - Query *parent_parse; - Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); - PlannerInfo **parent_roots = NULL; + List *tlist; + standard_qp_extra qp_extra; + RelOptInfo *planned_rel; + /* Inheritance is never used for insert. */ Assert(parse->commandType != CMD_INSERT); + parent_rte = planner_rt_fetch(top_parentRTindex, root); /* - * We generate a modified instance of the original Query for each target - * relation, plan that, and put all the plans into a list that will be - * controlled by a single ModifyTable node. All the instances share the - * same rangetable, but each instance must have its own set of subquery - * RTEs within the finished rangetable because (1) they are likely to get - * scribbled on during planning, and (2) it's not inconceivable that - * subqueries could get planned differently in different cases. We need - * not create duplicate copies of other RTE kinds, in particular not the - * target relations, because they don't have either of those issues. Not - * having to duplicate the target relations is important because doing so - * (1) would result in a rangetable of length O(N^2) for N targets, with - * at least O(N^3) work expended here; and (2) would greatly complicate - * management of the rowMarks list. + * Generate the access paths for all relations mentioned in the query, + * including the target inheritance set. When doing the join planning, + * references in the join tree to the original target relation that's the + * root parent of the inheritance tree is replaced by each of its + * inheritance children and the resulting joinrel RelOptInfo's are + * added to root->inh_target_child_rels. * - * To begin with, generate a bitmapset of the relids of the subquery RTEs. + * Final planning steps (grouping_planner) are applied to the best path + * of each of those child joinrels using a modified instance of the + * original query for a given child target rel. All the paths so generated + * are put into a list that will be controlled by a single ModifyTable + * node. All the instances share the same rangetable. */ - subqueryRTindexes = NULL; - rti = 1; - foreach(lc, parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - if (rte->rtekind == RTE_SUBQUERY) - subqueryRTindexes = bms_add_member(subqueryRTindexes, rti); - rti++; - } /* - * Next, we want to identify which AppendRelInfo items contain references - * to any of the aforesaid subquery RTEs. These items will need to be - * copied and modified to adjust their subquery references; whereas the - * other ones need not be touched. It's worth being tense over this - * because we can usually avoid processing most of the AppendRelInfo - * items, thereby saving O(N^2) space and time when the target is a large - * inheritance tree. We can identify AppendRelInfo items by their - * child_relid, since that should be unique within the list. + * Save the unexpanded version of the query's targetlist to be used below + * for passing to grouping_planner for each child target relation. */ - modifiableARIindexes = NULL; - if (subqueryRTindexes != NULL) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); + root->unexpanded_tlist = list_copy(root->parse->targetList); - if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) || - bms_is_member(appinfo->child_relid, subqueryRTindexes) || - bms_overlap(pull_varnos((Node *) appinfo->translated_vars), - subqueryRTindexes)) - modifiableARIindexes = bms_add_member(modifiableARIindexes, - appinfo->child_relid); - } + /* Do the scan/join planning. */ + tlist = preprocess_targetlist(root); + root->processed_tlist = tlist; + qp_extra.tlist = tlist; + qp_extra.activeWindows = qp_extra.groupClause = NIL; + planned_rel = query_planner(root, tlist, standard_qp_callback, &qp_extra); + + /* + * If it turned out during query planning that all the children are dummy + * (pruned or excluded by constraints), no need to do the steps below. + * Let grouping_planner finish up the final path. + */ + if (IS_DUMMY_REL(planned_rel)) + { + grouping_planner(root, false, planned_rel, 0.0); + return; } + Assert(planned_rel->relid == top_parentRTindex); + Assert(planned_rel->reloptkind == RELOPT_BASEREL); + /* * If the parent RTE is a partitioned table, we should use that as the * nominal target relation, because the RTEs added for partitioned tables @@ -1250,7 +1240,6 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1258,59 +1247,23 @@ inheritance_planner(PlannerInfo *root) } /* - * The PlannerInfo for each child is obtained by translating the relevant - * members of the PlannerInfo for its immediate parent, which we find - * using the parent_relid in its AppendRelInfo. We save the PlannerInfo - * for each parent in an array indexed by relid for fast retrieval. Since - * the maximum number of parents is limited by the number of RTEs in the - * query, we use that number to allocate the array. An extra entry is - * needed since relids start from 1. + * Get on with finalizing the path for each child target relation by + * calling grouping_planner on its joinrel. Note that we're restoring + * the query's targetlist to the original one for grouping_planner's + * targetlist expansion steps to perform the expansion with individual + * child descriptors. */ - parent_roots = (PlannerInfo **) palloc0((list_length(parse->rtable) + 1) * - sizeof(PlannerInfo *)); - parent_roots[top_parentRTindex] = root; - - /* - * And now we can get on with generating a plan for each child table. - */ - foreach(lc, root->append_rel_list) + forboth(lc1, root->inh_target_child_roots, + lc2, root->inh_target_child_rels) { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - PlannerInfo *subroot; + PlannerInfo *subroot = lfirst(lc1); + RelOptInfo *childrel = lfirst(lc2); + AppendRelInfo *appinfo; RangeTblEntry *child_rte; RelOptInfo *sub_final_rel; Path *subpath; - /* append_rel_list contains all append rels; ignore others */ - if (!bms_is_member(appinfo->parent_relid, parent_relids)) - continue; - - /* - * expand_inherited_rtentry() always processes a parent before any of - * that parent's children, so the parent_root for this relation should - * already be available. - */ - parent_root = parent_roots[appinfo->parent_relid]; - Assert(parent_root != NULL); - parent_parse = parent_root->parse; - - /* - * We need a working copy of the PlannerInfo so that we can control - * propagation of information back to the main copy. - */ - subroot = makeNode(PlannerInfo); - memcpy(subroot, parent_root, sizeof(PlannerInfo)); - - /* - * Generate modified query with this rel as target. We first apply - * adjust_appendrel_attrs, which copies the Query and changes - * references to the parent RTE to refer to the current child RTE, - * then fool around with subquery RTEs. - */ - subroot->parse = (Query *) - adjust_appendrel_attrs(parent_root, - (Node *) parent_parse, - 1, &appinfo); + appinfo = root->append_rel_array[subroot->parse->resultRelation]; /* * If there are securityQuals attached to the parent, move them to the @@ -1322,31 +1275,12 @@ inheritance_planner(PlannerInfo *root) parent_rte->securityQuals = NIL; /* - * Mark whether we're planning a query to a partitioned table or an - * inheritance parent. + * Ignore a partitioned child. Instead, the paths of its children will + * be added to subpaths. */ - subroot->inhTargetKind = - (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED; - - /* - * If this child is further partitioned, remember it as a parent. - * Since a partitioned table does not have any data, we don't need to - * create a plan for it, and we can stop processing it here. We do, - * however, need to remember its modified PlannerInfo for use when - * processing its children, since we'll update their varnos based on - * the delta from immediate parent to child, not from top to child. - * - * Note: a very non-obvious point is that we have not yet added - * duplicate subquery RTEs to the subroot's rtable. We mustn't, - * because then its children would have two sets of duplicates, - * confusing matters. - */ - if (child_rte->inh) + if (childrel->part_scheme) { - Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE); - parent_relids = bms_add_member(parent_relids, appinfo->child_relid); - parent_roots[appinfo->child_relid] = subroot; - + Assert(child_rte->inh); continue; } @@ -1374,111 +1308,8 @@ inheritance_planner(PlannerInfo *root) if (nominalRelation < 0) nominalRelation = appinfo->child_relid; - /* - * The rowMarks list might contain references to subquery RTEs, so - * make a copy that we can apply ChangeVarNodes to. (Fortunately, the - * executor doesn't need to see the modified copies --- we can just - * pass it the original rowMarks list.) - */ - subroot->rowMarks = copyObject(parent_root->rowMarks); - - /* - * The append_rel_list likewise might contain references to subquery - * RTEs (if any subqueries were flattenable UNION ALLs). So prepare - * to apply ChangeVarNodes to that, too. As explained above, we only - * want to copy items that actually contain such references; the rest - * can just get linked into the subroot's append_rel_list. - * - * If we know there are no such references, we can just use the outer - * append_rel_list unmodified. - */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - subroot->append_rel_list = NIL; - foreach(lc2, parent_root->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, modifiableARIindexes)) - appinfo2 = copyObject(appinfo2); - - subroot->append_rel_list = lappend(subroot->append_rel_list, - appinfo2); - } - } - - /* - * Add placeholders to the child Query's rangetable list to fill the - * RT indexes already reserved for subqueries in previous children. - * These won't be referenced, so there's no need to make them very - * valid-looking. - */ - while (list_length(subroot->parse->rtable) < list_length(final_rtable)) - subroot->parse->rtable = lappend(subroot->parse->rtable, - makeNode(RangeTblEntry)); - - /* - * If this isn't the first child Query, generate duplicates of all - * subquery RTEs, and adjust Var numbering to reference the - * duplicates. To simplify the loop logic, we scan the original rtable - * not the copy just made by adjust_appendrel_attrs; that should be OK - * since subquery RTEs couldn't contain any references to the target - * rel. - */ - if (final_rtable != NIL && subqueryRTindexes != NULL) - { - ListCell *lr; - - rti = 1; - foreach(lr, parent_parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lr); - - if (bms_is_member(rti, subqueryRTindexes)) - { - Index newrti; - - /* - * The RTE can't contain any references to its own RT - * index, except in its securityQuals, so we can save a - * few cycles by applying ChangeVarNodes to the rest of - * the rangetable before we append the RTE to it. - */ - newrti = list_length(subroot->parse->rtable) + 1; - ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); - ChangeVarNodes((Node *) subroot->rowMarks, rti, newrti, 0); - /* Skip processing unchanging parts of append_rel_list */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - foreach(lc2, subroot->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, - modifiableARIindexes)) - ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); - } - } - rte = copyObject(rte); - ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); - subroot->parse->rtable = lappend(subroot->parse->rtable, - rte); - } - rti++; - } - } - - /* There shouldn't be any OJ info to translate, as yet */ - Assert(subroot->join_info_list == NIL); - /* and we haven't created PlaceHolderInfos, either */ - Assert(subroot->placeholder_list == NIL); - - /* Generate Path(s) for accessing this result relation */ - grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ ); + /* Apply the top-level targetlist to childrel's path. */ + grouping_planner(subroot, true, childrel, 0.0); /* * Select cheapest path in case there's more than one. We always run @@ -1490,45 +1321,10 @@ inheritance_planner(PlannerInfo *root) subpath = sub_final_rel->cheapest_total_path; /* - * If this child rel was excluded by constraint exclusion, exclude it - * from the result plan. + * child rel cannot be empty, or inheritance_make_rel_from_joinlist + * wouldn't have put it in the list to begin with. */ - if (IS_DUMMY_PATH(subpath)) - continue; - - /* - * If this is the first non-excluded child, its post-planning rtable - * becomes the initial contents of final_rtable; otherwise, append - * just its modified subquery RTEs to final_rtable. - */ - if (final_rtable == NIL) - final_rtable = subroot->parse->rtable; - else - final_rtable = list_concat(final_rtable, - list_copy_tail(subroot->parse->rtable, - list_length(final_rtable))); - - /* - * We need to collect all the RelOptInfos from all child plans into - * the main PlannerInfo, since setrefs.c will need them. We use the - * last child's simple_rel_array (previous ones are too short), so we - * have to propagate forward the RelOptInfos that were already built - * in previous children. - */ - Assert(subroot->simple_rel_array_size >= save_rel_array_size); - for (rti = 1; rti < save_rel_array_size; rti++) - { - RelOptInfo *brel = save_rel_array[rti]; - - if (brel) - subroot->simple_rel_array[rti] = brel; - } - save_rel_array_size = subroot->simple_rel_array_size; - save_rel_array = subroot->simple_rel_array; - save_append_rel_array = subroot->append_rel_array; - - /* Make sure any initplans from this rel get into the outer list */ - root->init_plans = subroot->init_plans; + Assert(!IS_DUMMY_PATH(subpath)); /* Build list of sub-paths */ subpaths = lappend(subpaths, subpath); @@ -1560,36 +1356,6 @@ inheritance_planner(PlannerInfo *root) */ /* - * If we managed to exclude every child rel, return a dummy plan; it - * doesn't even need a ModifyTable node. - */ - if (subpaths == NIL) - { - set_dummy_rel_pathlist(final_rel); - return; - } - - /* - * Put back the final adjusted rtable into the master copy of the Query. - * (We mustn't do this if we found no non-excluded children.) - */ - parse->rtable = final_rtable; - root->simple_rel_array_size = save_rel_array_size; - root->simple_rel_array = save_rel_array; - root->append_rel_array = save_append_rel_array; - - /* Must reconstruct master's simple_rte_array, too */ - root->simple_rte_array = (RangeTblEntry **) - palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *)); - rti = 1; - foreach(lc, final_rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - root->simple_rte_array[rti++] = rte; - } - - /* * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will * have dealt with fetching non-locked marked rows, else we need to have * ModifyTable do that. @@ -1629,6 +1395,12 @@ inheritance_planner(PlannerInfo *root) * (inheritance_planner will create a single ModifyTable node covering all the * target tables.) * + * If non-NULL, planned_rel is a RelOptInfo containing paths for the query's + * top-level joinrel, which the caller produced by itself. In that case, this + * function only needs to adjust the targetlist of its cheapest_total_path. + * The only caller that may pass such a RelOptInfo currently is + * inheritance_planner. + * * tuple_fraction is the fraction of tuples we expect will be retrieved. * tuple_fraction is interpreted as follows: * 0: expect all tuples to be retrieved (normal case) @@ -1647,6 +1419,7 @@ inheritance_planner(PlannerInfo *root) */ static void grouping_planner(PlannerInfo *root, bool inheritance_update, + RelOptInfo *planned_rel, double tuple_fraction) { Query *parse = root->parse; @@ -1659,7 +1432,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, List *final_targets; List *final_targets_contain_srfs; bool final_target_parallel_safe; - RelOptInfo *current_rel; + RelOptInfo *current_rel = planned_rel; RelOptInfo *final_rel; ListCell *lc; @@ -1699,6 +1472,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * special work for recursive unions is the responsibility of * plan_set_operations. */ + Assert(current_rel == NULL); current_rel = plan_set_operations(root); /* @@ -1788,17 +1562,26 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, parse->groupClause = preprocess_groupclause(root, NIL); } - /* Preprocess targetlist */ - tlist = preprocess_targetlist(root); - /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation so - * that we can transfer its decoration (resnames etc) to the topmost - * tlist of the finished Plan. + * Preprocess targetlist, if needed. If the caller has already done + * query planning, root->processed_tlist already contain the desired + * targetlist. */ - root->processed_tlist = tlist; + if (planned_rel == NULL) + { + tlist = preprocess_targetlist(root); + + /* + * We are now done hacking up the query's targetlist. Most of the + * remaining planning work will be done with the PathTarget + * representation of tlists, but save aside the full representation + * so that we can transfer its decoration (resnames etc) to the + * topmost tlist of the finished Plan. + */ + root->processed_tlist = tlist; + } + else + tlist = root->processed_tlist; /* * Collect statistics about aggregates for estimating costs, and mark @@ -1878,8 +1661,9 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * We also generate (in standard_qp_callback) pathkey representations * of the query's sort clause, distinct clause, etc. */ - current_rel = query_planner(root, tlist, - standard_qp_callback, &qp_extra); + if (current_rel == NULL) + current_rel = query_planner(root, tlist, + standard_qp_callback, &qp_extra); /* * Convert the query's result tlist into PathTarget format. diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index cd6e11904e..78baec00dc 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -917,7 +917,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->grouping_map = NULL; subroot->minmax_aggs = NIL; subroot->qual_security_level = 0; - subroot->inhTargetKind = INHKIND_NONE; subroot->hasRecursion = false; subroot->wt_param_id = -1; subroot->non_recursive_path = NULL; diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index d5720518a8..ef1f978889 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -2253,8 +2253,39 @@ adjust_appendrel_attrs_mutator(Node *node, context->appinfos); return (Node *) phv; } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 0c88c90de4..ed0953f9e1 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1264,36 +1264,6 @@ get_relation_constraints(PlannerInfo *root, } } - /* - * Append partition predicates, if any. - * - * For selects, partition pruning uses the parent table's partition bound - * descriptor, instead of constraint exclusion which is driven by the - * individual partition's partition constraint. - */ - if (enable_partition_pruning && root->parse->commandType != CMD_SELECT) - { - List *pcqual = RelationGetPartitionQual(relation); - - if (pcqual) - { - /* - * Run the partition quals through const-simplification similar to - * check constraints. We skip canonicalize_qual, though, because - * partition quals should be in canonical form already; also, - * since the qual is in implicit-AND format, we'd have to - * explicitly convert it to explicit-AND format and back again. - */ - pcqual = (List *) eval_const_expressions(root, (Node *) pcqual); - - /* Fix Vars to have the desired varno */ - if (varno != 1) - ChangeVarNodes((Node *) pcqual, 1, varno, 0); - - result = list_concat(result, pcqual); - } - } - heap_close(relation, NoLock); return result; @@ -1420,31 +1390,15 @@ relation_excluded_by_constraints(PlannerInfo *root, switch (constraint_exclusion) { case CONSTRAINT_EXCLUSION_OFF: - - /* - * Don't prune if feature turned off -- except if the relation is - * a partition. While partprune.c-style partition pruning is not - * yet in use for all cases (update/delete is not handled), it - * would be a UI horror to use different user-visible controls - * depending on such a volatile implementation detail. Therefore, - * for partitioned tables we use enable_partition_pruning to - * control this behavior. - */ - if (root->inhTargetKind == INHKIND_PARTITIONED) - break; return false; case CONSTRAINT_EXCLUSION_PARTITION: /* * When constraint_exclusion is set to 'partition' we only handle - * OTHER_MEMBER_RELs, or BASERELs in cases where the result target - * is an inheritance parent or a partitioned table. + * OTHER_MEMBER_RELs. */ - if ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) && - !(rel->reloptkind == RELOPT_BASEREL && - root->inhTargetKind != INHKIND_NONE && - rel->relid == root->parse->resultRelation)) + if (rel->reloptkind != RELOPT_OTHER_MEMBER_REL) return false; break; diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 6fd24203dd..2ad3dc4711 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -319,9 +319,6 @@ typedef struct PlannerInfo Index qual_security_level; /* minimum security_level for quals */ /* Note: qual_security_level is zero if there are no securityQuals */ - InheritanceKind inhTargetKind; /* indicates if the target relation is an - * inheritance child or partition or a - * partitioned table */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */ bool hasDeletedRTEs; /* true if any RTE was deleted from jointree */ @@ -343,6 +340,34 @@ typedef struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* + * The following fields are set during query planning portion of an + * inherited UPDATE/DEELETE operation. + */ + + /* + * This stores the original version of the query's targetlist that's + * not modified by the planner. + */ + List *unexpanded_tlist; + + /* + * List containing a PlannerInfo corresponding to each child target rel. + * Content of each PlannerInfo is same as the parent PlannerInfo, except + * for the parse tree which is a translated copy of the parent's parse + * tree. + */ + List *inh_target_child_roots; + + /* + * RelOptInfos corresponding to each child target rel. For leaf children, + * it's the RelOptInfo representing the output of make_rel_from_joinlist() + * called with the parent rel in the original join tree replaced by a + * given leaf child. For non-leaf children, it's the baserel RelOptInfo + * itself, left as a placeholder. + */ + List *inh_target_child_rels; } PlannerInfo; diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index 3ba3aaf2d8..a539280851 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1781,7 +1781,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_1 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_1 -> Nested Loop Semi Join @@ -1789,7 +1789,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_2 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_2 (28 rows) -- 2.11.0
v7-0002-Store-inheritance-root-parent-index-in-otherrel-s.patch (text/plain, 2.5K) download From b96ef2f769ad224cad0cb5b2050f72b13e85770f Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 26 Oct 2018 16:45:59 +0900 Subject: [PATCH v7 2/6] Store inheritance root parent index in otherrel's RelOptInfo Although it's set by build_simple_rel, it's not being used by any code yet. --- src/backend/nodes/outfuncs.c | 1 + src/backend/optimizer/util/relnode.c | 14 ++++++++++++++ src/include/nodes/relation.h | 4 ++++ 3 files changed, 19 insertions(+) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 08657fcfd9..1d7c420e75 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2370,6 +2370,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_BOOL_FIELD(consider_partitionwise_join); WRITE_BITMAPSET_FIELD(top_parent_relids); WRITE_NODE_FIELD(partitioned_child_rels); + WRITE_UINT_FIELD(inh_root_parent); } static void diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 39f5729b91..29ba19349f 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -215,9 +215,23 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->top_parent_relids = parent->top_parent_relids; else rel->top_parent_relids = bms_copy(parent->relids); + + /* + * For inheritance child relations, we also set inh_root_parent. + * Note that 'parent' might itself be a child (a sub-partitioned + * partition), in which case we simply use its value of + * inh_root_parent. + */ + if (parent->rtekind == RTE_RELATION) + rel->inh_root_parent = parent->inh_root_parent > 0 ? + parent->inh_root_parent : + parent->relid; } else + { rel->top_parent_relids = NULL; + rel->inh_root_parent = 0; + } /* Check type of rtable entry */ switch (rte->rtekind) diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 2ad3dc4711..94f14019bd 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -728,6 +728,10 @@ typedef struct RelOptInfo List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ + + Index inh_root_parent; /* For otherrels, this is the RT index of + * inheritance table mentioned in the query + * from which this relation originated */ } RelOptInfo; /* -- 2.11.0
v7-0003-Lazy-creation-of-RTEs-for-inheritance-children.patch (text/plain, 85.4K) download From b2ac351c356a95e32488de10fcf8dbec1816d6a7 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 18:14:13 +0900 Subject: [PATCH v7 3/6] Lazy creation of RTEs for inheritance children Currently, expand_inherited_tables called from subquery_planner opens all inheritance child tables and adds them to the query's range table and PlannerInfo in the form of AppendRelInfo's initially and later RelOptInfo's. For partitioning, that's pretty wasteful, because it's possible to determine only the partitions that will need to be scanned using partition pruning, which doesn't require opening the partitions themselves. However, the point at which the above processing happens is too early to perform partition pruning. This commit rearranges things within the planner so that inheritance child relations can be added to the Query and PlannerInfo during make_one_rel, just before set_base_rel_sizes is called. Although the late initialization approach only benefits the partitioning case, it seems undesirable to do it only for partitioned tables, because it means adding code to handle partitions specially at various places within the planner. So, *all* inheritance parent tables are expanded in make_one_rel. All unpruned partitions are added to Query in the form of their RangeTblEntry's being added to the range table and to PlannerInfo in the form of an AppendRelInfo and a RelOptInfo for each. For partitioning, although we don't create a RangeTblEntry and RelOptInfo for pruned partitions at make_one_rel time, partitionwise join code relies on the fact that even though partitions may have been pruned, they'd still own a RelOptInfo to handle the outer join case where the pruned partition appears on the nullable side of join. Partitionwise join code deals with that by allocating dummy RelOptInfos for pruned partitions that are based mostly on their parent's properties. There are some regression test diffs: 1. Caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child, as seen in the partition_aggregate.out test output. 2. Those in postgres_fdw.out are caused by the fact that junk columns required for row marking are added to reltarget->exprs later than user columns, because the row marking junk columns arent added until after the inheritance is expanded which as of this commit is later than it used to be. --- contrib/postgres_fdw/expected/postgres_fdw.out | 32 +- src/backend/optimizer/path/allpaths.c | 222 ++---- src/backend/optimizer/path/joinrels.c | 62 ++ src/backend/optimizer/plan/initsplan.c | 58 -- src/backend/optimizer/plan/planner.c | 60 +- src/backend/optimizer/plan/setrefs.c | 6 + src/backend/optimizer/prep/preptlist.c | 15 +- src/backend/optimizer/prep/prepunion.c | 852 +++++++++++++++------- src/backend/optimizer/util/Makefile | 2 +- src/backend/optimizer/util/plancat.c | 40 +- src/backend/optimizer/util/relnode.c | 80 +- src/backend/partitioning/partprune.c | 43 +- src/include/nodes/relation.h | 8 + src/include/optimizer/pathnode.h | 1 + src/include/optimizer/plancat.h | 2 +- src/include/optimizer/prep.h | 9 +- src/include/partitioning/partprune.h | 2 +- src/test/regress/expected/partition_aggregate.out | 4 +- 18 files changed, 882 insertions(+), 616 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 21a2ef5ad3..f546260af8 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7085,15 +7085,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7123,15 +7123,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7160,15 +7160,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid @@ -7178,15 +7178,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (39 rows) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 7787592870..a4dd34f2cd 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -44,7 +44,6 @@ #include "optimizer/var.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -180,6 +179,16 @@ make_one_rel(PlannerInfo *root, List *joinlist) set_base_rel_consider_startup(root); /* + * Expand RT entries that represent inherited or partitioned tables. + * This will perform partition pruning on partitioned tables in the + * original range table and also recursively on any child partitioned + * tables that were added by the expansion of the original parent(s). + * As new entries are added to the range table, various arrays in the + * PlannerInfo will be expanded accordingly. + */ + expand_inherited_tables(root); + + /* * Compute size estimates and consider_parallel flags for each base rel. */ set_base_rel_sizes(root); @@ -404,7 +413,11 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, */ set_dummy_rel_pathlist(rel); } - else if (rte->inh) + /* + * expand_inherited_tables may have proved that the relation is empty, so + * check if it's so. + */ + else if (rte->inh && !IS_DUMMY_REL(rel)) { /* It's an "append relation", process accordingly */ set_append_rel_size(root, rel, rti, rte); @@ -957,8 +970,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -966,32 +977,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Assert(IS_SIMPLE_REL(rel)); /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, we will bubble up - * the indexes of partitioned relations that appear down in the tree, so - * that when we've created Paths for all the children, the root - * partitioned table's list will contain all such indexes. - */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - rel->partitioned_child_rels = list_make1_int(rti); - - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - - /* * If this is a partitioned baserel, set the consider_partitionwise_join * flag; currently, we only consider partitionwise joins with the baserel * if its targetlist doesn't contain a whole-row Var. @@ -1028,12 +1013,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; ListCell *parentvars; ListCell *childvars; - ListCell *lc; PlannerInfo *subroot = root; /* append_rel_list contains all append rels; ignore others */ @@ -1042,18 +1023,34 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo is created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); /* - * Copy/Modify targetlist. Even if this child is deemed empty, we need - * its targetlist in case it falls on nullable side in a child-join - * because of partitionwise join. + * Child relation may have be marked dummy if build_append_child_rel + * found self-contradictory quals. + */ + if (IS_DUMMY_REL(childrel)) + continue; + + if (relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* + * This child need not be scanned, so we can omit it from the + * appendrel. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Copy/Modify targetlist. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -1082,144 +1079,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, add_child_rel_equivalences(root, appinfo, rel, childrel); childrel->has_eclass_joins = rel->has_eclass_joins; - /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* - * This child need not be scanned, so we can omit it from the - * appendrel. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - /* CE failed, so finish copying/modifying join quals. */ childrel->joininfo = (List *) adjust_appendrel_attrs(root, @@ -1284,8 +1143,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, /* * If the childrel itself was a partitioned table, its children - * would've been added into subroot. Copy relevant fields - * into the parent root. + * would've been added into subroot. */ if (subroot->inh_target_child_roots != NIL) root->inh_target_child_roots = @@ -1442,9 +1300,10 @@ adjust_inherit_target_child(PlannerInfo *root, RelOptInfo *childrel, /* * Apply planner's expansion of targetlist, such as adding various junk * column, filling placeholder entries for dropped columns, etc., all of - * which occurs with the child's TupleDesc. + * which occurs with the child's TupleDesc. Since inheritance has already + * been expanded (we wouldn't be here otherwise), pass true. */ - tlist = preprocess_targetlist(subroot); + tlist = preprocess_targetlist(subroot, true); subroot->processed_tlist = tlist; build_base_rel_tlists(subroot, tlist); @@ -2826,6 +2685,7 @@ inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) Assert(subroot->parse->resultRelation > 0); childrel = find_base_rel(root, subroot->parse->resultRelation); + Assert(childrel != NULL); appinfo = root->append_rel_array[subroot->parse->resultRelation]; if (appinfo->parent_relid != root->parse->resultRelation) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index d3d21fed5d..da0831de4e 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -20,6 +20,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/prep.h" +#include "optimizer/tlist.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -46,6 +47,9 @@ static void try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, List *parent_restrictlist); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + int partidx); /* @@ -1376,6 +1380,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts); + if (child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts); + /* We should never try to join two overlapping sets of rels. */ Assert(!bms_overlap(child_rel1->relids, child_rel2->relids)); child_joinrelids = bms_union(child_rel1->relids, child_rel2->relids); @@ -1582,3 +1591,56 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx) +{ + RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + /* Pass parent's info as for both the parent rel and child rel. */ + AppendRelInfo *appinfo = make_append_rel_info(parent, parentrte, + parent->tupdesc, + parentrte->relid, + parent->reltype, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 01335db511..7636aa82c4 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -616,64 +616,6 @@ create_lateral_join_info(PlannerInfo *root) bms_add_member(brel2->lateral_referencers, rti); } } - - /* - * Lastly, propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - */ - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - RangeTblEntry *brte = root->simple_rte_array[rti]; - - /* - * Skip empty slots. Also skip non-simple relations i.e. dead - * relations. - */ - if (brel == NULL || !IS_SIMPLE_REL(brel)) - continue; - - /* - * In the case of table inheritance, the parent RTE is directly linked - * to every child table via an AppendRelInfo. In the case of table - * partitioning, the inheritance hierarchy is expanded one level at a - * time rather than flattened. Therefore, an other member rel that is - * a partitioned table may have children of its own, and must - * therefore be marked with the appropriate lateral info so that those - * children eventually get marked also. - */ - Assert(brte); - if (brel->reloptkind == RELOPT_OTHER_MEMBER_REL && - (brte->rtekind != RTE_RELATION || - brte->relkind != RELKIND_PARTITIONED_TABLE)) - continue; - - if (brte->inh) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); - RelOptInfo *childrel; - - if (appinfo->parent_relid != rti) - continue; - childrel = root->simple_rel_array[appinfo->child_relid]; - Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - Assert(childrel->direct_lateral_relids == NULL); - childrel->direct_lateral_relids = brel->direct_lateral_relids; - Assert(childrel->lateral_relids == NULL); - childrel->lateral_relids = brel->lateral_relids; - Assert(childrel->lateral_referencers == NULL); - childrel->lateral_referencers = brel->lateral_referencers; - } - } - } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index d094b3092b..5747694f63 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -23,6 +23,7 @@ #include "access/sysattr.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -699,27 +700,24 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } /* * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). + * pullup (so that all non-inherited RTEs are present). */ preprocess_rowmarks(root); /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain base relations not joins, so it's OK (and marginally more - * efficient) to do it after checking for join RTEs. We must do it after - * pulling up subqueries, else we'd fail to handle inherited tables in - * subqueries. - */ - expand_inherited_tables(root); - - /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -1212,8 +1210,11 @@ inheritance_planner(PlannerInfo *root) */ root->unexpanded_tlist = list_copy(root->parse->targetList); - /* Do the scan/join planning. */ - tlist = preprocess_targetlist(root); + /* + * Do the scan/join planning. We haven't expanded inheritance yet, so + * pass false. + */ + tlist = preprocess_targetlist(root, false); root->processed_tlist = tlist; qp_extra.tlist = tlist; qp_extra.activeWindows = qp_extra.groupClause = NIL; @@ -1222,9 +1223,10 @@ inheritance_planner(PlannerInfo *root) /* * If it turned out during query planning that all the children are dummy * (pruned or excluded by constraints), no need to do the steps below. - * Let grouping_planner finish up the final path. + * For regular inheritance case, it's possible that we only need to modify + * the parent table. Let grouping_planner finish up the final path. */ - if (IS_DUMMY_REL(planned_rel)) + if (!parent_rte->inh || IS_DUMMY_REL(planned_rel)) { grouping_planner(root, false, planned_rel, 0.0); return; @@ -1569,7 +1571,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, */ if (planned_rel == NULL) { - tlist = preprocess_targetlist(root); + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root, false); /* * We are now done hacking up the query's targetlist. Most of the @@ -1666,6 +1669,15 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, standard_qp_callback, &qp_extra); /* + * Query planning may have added some columns to the top-level tlist, + * which happens when there are row marks applied to inheritance + * parent relations (additional junk columns needed for applying row + * marks are added after expanding inheritance.) + */ + if (list_length(tlist) < list_length(root->processed_tlist)) + tlist = root->processed_tlist; + + /* * Convert the query's result tlist into PathTarget format. * * Note: it's desirable to not do this till after query_planner(), @@ -2352,7 +2364,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2377,7 +2389,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } @@ -6757,6 +6769,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -6858,6 +6874,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 6d6ef1c376..454870609a 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -326,6 +326,12 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) UPPERREL_FINAL, NULL))) add_rtes_to_flat_rtable(rel->subroot, true); } + /* + * A NULL rel also means an unplanned subquery rte, so apply + * flatten_unplanned_rtes. + */ + else + flatten_unplanned_rtes(glob, rte); } rti++; } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 8603feef2b..0747403acd 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -65,9 +65,13 @@ static List *expand_targetlist(List *tlist, int command_type, * * As a side effect, if there's an ON CONFLICT UPDATE clause, its targetlist * is also preprocessed (and updated in-place). + * + * inheritance_expanded specifies whether inheritance tables are expanded + * as of calling this function. Caller must specify it because that affects + * which row marking related junk columns get added to the targetlist. */ List * -preprocess_targetlist(PlannerInfo *root) +preprocess_targetlist(PlannerInfo *root, bool inheritance_expanded) { Query *parse = root->parse; int result_relation = parse->resultRelation; @@ -134,6 +138,15 @@ preprocess_targetlist(PlannerInfo *root) if (rc->rti != rc->prti) continue; + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which will change the parent row + * mark's allMarkTypes fields. + */ + if (rc->isParent && !inheritance_expanded) + continue; + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) { /* Need to fetch TID */ diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index ef1f978889..e4d4e7d6dd 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -39,15 +39,19 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" +#include "optimizer/var.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" +#include "partitioning/partprune.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/selfuncs.h" @@ -99,23 +103,26 @@ static List *generate_append_tlist(List *colTypes, List *colCollations, List *input_tlists, List *refnames_tlist); static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist); +static void expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti); static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); + Index rti, RelOptInfo *rel); static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); -static void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p); +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); +static RelOptInfo *build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, @@ -1472,33 +1479,134 @@ generate_setop_grouplist(SetOperationStmt *op, List *targetlist) void expand_inherited_tables(PlannerInfo *root) { - Index nrtes; + int orig_rtable_size; Index rti; - ListCell *rl; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; /* - * expand_inherited_rtentry may add RTEs to parse->rtable. The function is + * expand_append_rtentry may add RTEs to parse->rtable. The function is * expected to recursively handle any RTEs that it creates with inh=true. * So just scan as far as the original end of the rtable list. */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) + for (rti = 1; rti < orig_rtable_size; rti++) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + if (rte->inh) + expand_append_rtentry(root, brel, rte, rti); + } +} + +/* + * expand_append_rtentry + * This initializes RelOptInfos for an appendrel's child relations, if + * any + * + * 'rel' is the appendrel parent, whose range table entry ('rte') has been + * marked to require adding children. An appendrel parent could either + * be a subquery (if we flattened UNION ALL query) or a table that's known + * to have inheritance children. The latter consists of both regular + * inheritance parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as an appendrel child. The original RTE is + * considered to represent the whole inheritance set. + */ +static void +expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, + Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_append_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_inherited_rtentry(root, rte, rti, rel); } } /* * expand_inherited_rtentry - * Check whether a rangetable entry represents an inheritance set. - * If so, add entries for all the child tables to the query's - * rangetable, and build AppendRelInfo nodes for all the child tables - * and add them to root->append_rel_list. If not, clear the entry's - * "inh" flag to prevent later code from looking for AppendRelInfos. + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. * * Note that the original RTE is considered to represent the whole * inheritance set. The first of the generated RTEs is an RTE for the same @@ -1509,45 +1617,22 @@ expand_inherited_tables(PlannerInfo *root) * regular inheritance, a parent RTE must always have at least two associated * AppendRelInfos: one corresponding to the parent table as a simple member of * inheritance set and one or more corresponding to the actual children. - * Since a partitioned table is not scanned, it might have only one associated - * AppendRelInfo. */ static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, + RelOptInfo *rel) { Oid parentOID; PlanRowMark *oldrc; - Relation oldrelation; - LOCKMODE lockmode; + LOCKMODE lockmode = rte->rellockmode; List *inhOIDs; ListCell *l; + int num_children; + int num_children_added = 0; - /* Does RT entry allow inheritance? */ - if (!rte->inh) - return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } - /* Fast path for common case of childless table */ + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); parentOID = rte->relid; - if (!has_subclass(parentOID)) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * The rewriter should already have obtained an appropriate lock on each - * relation named in the query. However, for each child relation we add - * to the query, we must obtain an appropriate lock, because this will be - * the first use of those relations in the parse/rewrite/plan pipeline. - * Child rels should use the same lockmode as their parent. - */ - lockmode = rte->rellockmode; /* Scan for all members of inheritance set, acquire needed locks */ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); @@ -1557,7 +1642,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) * case. This could happen despite above has_subclass() check, if table * once had a child but no longer does. */ - if (list_length(inhOIDs) < 2) + num_children = list_length(inhOIDs); + if (num_children < 2) { /* Clear flag before returning */ rte->inh = false; @@ -1565,219 +1651,220 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } /* - * If parent relation is selected FOR UPDATE/SHARE, we need to mark its - * PlanRowMark as isParent = true, and generate a new PlanRowMark for each - * child. + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. */ oldrc = get_plan_rowmark(root->rowMarks, rti); - if (oldrc) - oldrc->isParent = true; + Assert(oldrc == NULL || oldrc->isParent); /* - * Must open the parent relation to examine its tupdesc. We need not lock - * it; we assume the rewriter already did. + * Must expand PlannerInfo arrays by num_children before we can add + * children. */ - oldrelation = heap_open(parentOID, NoLock); + expand_planner_arrays(root, num_children); - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) + foreach(l, inhOIDs) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); - - /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. - */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); - } - else - { - List *appinfos = NIL; + Oid childOID = lfirst_oid(l); + Relation newrelation; RangeTblEntry *childrte; Index childRTindex; + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. */ - foreach(l, inhOIDs) + if (RELATION_IS_OTHER_TEMP(newrelation)) { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = heap_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - heap_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - heap_close(newrelation, NoLock); + heap_close(newrelation, lockmode); + continue; } - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); + (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation, &childrte, + &childRTindex); + Assert(childrte != NULL); + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + Assert(childRTindex > 0); + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; } - heap_close(oldrelation, NoLock); + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } } /* * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo * - * Note that RelationGetPartitionDispatchInfo will expand partitions in the - * same order as this code. + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * in order to avoid partitions being locked in a different order than other + * places in the backend that may lock partitions. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) + Index parentRTindex, RelOptInfo *parentrel) { + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; - check_stack_depth(); - - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + expand_planner_arrays(root, bms_num_members(partindexes)); /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. */ - if (partdesc->nparts == 0) - { - parentrte->inh = false; - return; - } + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); - for (i = 0; i < partdesc->nparts; i++) + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) { - Oid childOID = partdesc->oids[i]; - Relation childrel; + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + RangeTblEntry *childrte; + Index childRTindex; - /* Open rel; we already have required locks */ - childrel = heap_open(childOID, NoLock); + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation, + &childrte, &childRTindex); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + + /* If the child is partitioned itself, expand it too. */ + if (childrel->part_scheme) + { + Assert(childrte->inh); expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); + childrel); + } + } - /* Close child relation, but keep locks */ - heap_close(childrel, NoLock); + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); } } /* - * expand_single_inheritance_child - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) + * The return value is the RelOptInfo that's added. * * PlanRowMarks still carry the top-parent's RTI, and the top-parent's * allMarkTypes field still accumulates values from all descendents. * * "parentrte" and "parentRTindex" are immediate parent's RTE and * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. */ -static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); Oid childOID = RelationGetRelid(childrel); RangeTblEntry *childrte; Index childRTindex; AppendRelInfo *appinfo; + RelOptInfo *childrelopt; /* * Build an RTE for the child, and attach to query's rangetable list. We * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh = false. Also, set requiredPerms to zero since - * all required permissions checks are done on the original RTE. Likewise, - * set the child's securityQuals to empty, because we only want to apply - * the parent's RLS conditions regardless of what RLS properties + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties * individual children may have. (This is an intentional choice to make * inherited RLS work like regular permissions checks.) The parent * securityQuals will be propagated to children along with other base @@ -1787,55 +1874,42 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childrte_p = childrte; childrte->relid = childOID; childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) - childrte->inh = true; - else - childrte->inh = false; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); childrte->requiredPerms = 0; childrte->securityQuals = NIL; parse->rtable = lappend(parse->rtable, childrte); childRTindex = list_length(parse->rtable); *childRTindex_p = childRTindex; - /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. - */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) - { - appinfo = makeNode(AppendRelInfo); - appinfo->parent_relid = parentRTindex; - appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->rd_rel->reltype; - appinfo->child_reltype = childrel->rd_rel->reltype; - make_inh_translation_list(parentrel, childrel, childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = parentOID; - *appinfos = lappend(*appinfos, appinfo); + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + childrel->rd_att, + childOID, + childrel->rd_rel->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); } /* @@ -1867,6 +1941,24 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need need to create + * one, because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_append_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; } /* @@ -1877,14 +1969,11 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, * For paranoia's sake, we match type/collation as well as attribute name. */ static void -make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, - List **translated_vars) +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) { List *vars = NIL; - TupleDesc old_tupdesc = RelationGetDescr(oldrelation); - TupleDesc new_tupdesc = RelationGetDescr(newrelation); - Oid new_relid = RelationGetRelid(newrelation); int oldnatts = old_tupdesc->natts; int newnatts = new_tupdesc->natts; int old_attno; @@ -1914,7 +2003,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, * When we are generating the "translation list" for the parent table * of an inheritance set, no need to search for matches. */ - if (oldrelation == newrelation) + if (from_rel == to_rel) { vars = lappend(vars, makeVar(newvarno, (AttrNumber) (old_attno + 1), @@ -1940,10 +2029,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, { HeapTuple newtup; - newtup = SearchSysCacheAttName(new_relid, attname); + newtup = SearchSysCacheAttName(to_rel, attname); if (!newtup) elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; ReleaseSysCache(newtup); @@ -1953,10 +2042,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, /* Found it, check type and collation match */ if (atttypid != att->atttypid || atttypmod != att->atttypmod) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); if (attcollation != att->attcollation) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); vars = lappend(vars, makeVar(newvarno, (AttrNumber) (new_attno + 1), @@ -2023,6 +2112,255 @@ translate_col_privs(const Bitmapset *parent_privs, } /* + * build_append_child_rel + * Build a RelOptInfo for child relation of an append rel + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields base on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turns out to be self-contradictory, the + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + List *childquals; + ListCell *lc; + bool have_const_false_cq; + Index cq_min_security; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = false; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, parent->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual = (Node *) rinfo->clause; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, childqual, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might + * have securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals.) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except + * that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* + * not likely that we'd see constants here, so no + * check + */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, + false, + false, + security_level, + NULL, NULL, + NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* Set child's version of baserestrictinfo. */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; +} + +AppendRelInfo * +make_append_rel_info(RelOptInfo *parentrel, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex) +{ + AppendRelInfo *appinfo = makeNode(AppendRelInfo); + + appinfo->parent_relid = parentrel->relid; + appinfo->child_relid = childRTindex; + appinfo->parent_reltype = parentrel->reltype; + appinfo->child_reltype = childreltype; + make_inh_translation_list(parentrel->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, + &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; + + return appinfo; +} + +/* * adjust_appendrel_attrs * Copy the specified query or expression and translate Vars referring to a * parent rel to refer to the corresponding child rel instead. We also diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile index c54d0a690d..da9ccf32b4 100644 --- a/src/backend/optimizer/util/Makefile +++ b/src/backend/optimizer/util/Makefile @@ -13,6 +13,6 @@ top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global OBJS = clauses.o joininfo.o orclauses.o pathnode.o placeholder.o \ - plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o + plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index ed0953f9e1..92805046c8 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -106,7 +106,7 @@ static void set_baserel_partition_key_exprs(Relation relation, */ void get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) + Bitmapset *updatedCols, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; @@ -444,11 +444,31 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, get_relation_foreign_keys(root, rel, relation, inhparent); /* - * Collect info about relation's partitioning scheme, if any. Only - * inheritance parents may be partitioned. + * Collect some additional information for inheritance parents. */ - if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - set_relation_partition_info(root, rel, relation); + if (inhparent) + { + /* + * We'll need the TupleDesc when initializing the child relation. + * A copy is being made because concurrent changes might drop + * the relcache entry. That's possible because ALTER TABLE + * child_table NO INHERIT parent_table only requires an + * AccessShareLock on parent_table. + */ + rel->tupdesc = CreateTupleDescCopy(RelationGetDescr(relation)); + rel->reltype = RelationGetForm(relation)->reltype; + + /* + * If partitioned, also save the information of partitioning scheme, + * and whether the query updates any of the partition key columns. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + set_relation_partition_info(root, rel, relation); + root->partColsUpdated |= has_partition_attrs(relation, updatedCols, + NULL); + } + } heap_close(relation, NoLock); @@ -1854,16 +1874,20 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; + + /* + * Since we must've taken a lock on the table, it's okay to simply copy + * the pointers to relcache data here. + */ + rel->part_oids = partdesc->oids; + rel->boundinfo = partdesc->boundinfo; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 29ba19349f..c23db9d78d 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -130,6 +130,37 @@ setup_append_rel_array(PlannerInfo *root) } } +/* Expand the PlannerInfo arrays by add_size members and zero-init it. */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size = root->simple_rel_array_size + add_size; + + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + if (root->append_rel_array) + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + else + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + + /* Set the contents of just allocated memory to 0. */ + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + root->simple_rel_array_size = new_size; +} + /* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. @@ -238,7 +269,8 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte->relid, rte->inh, rte->updatedCols, + rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -279,52 +311,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); - /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. - */ - if (rte->inh) - { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); - - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; - } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); - } - return rel; } diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 35c87535d3..fcf8d6032c 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -45,6 +45,7 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/planner.h" #include "optimizer/predtest.h" @@ -436,17 +437,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -540,23 +547,20 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ @@ -564,6 +568,13 @@ prune_append_rel_partitions(RelOptInfo *rel) return NULL; /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. */ @@ -590,15 +601,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 94f14019bd..e6fdbcd030 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -15,6 +15,7 @@ #define RELATION_H #include "access/sdir.h" +#include "access/tupdesc.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/params.h" @@ -723,6 +724,7 @@ typedef struct RelOptInfo int nparts; /* number of partitions */ struct PartitionBoundInfoData *boundinfo; /* Partition bounds */ List *partition_qual; /* partition constraint */ + Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ List **partexprs; /* Non-nullable partition key expressions. */ @@ -732,6 +734,12 @@ typedef struct RelOptInfo Index inh_root_parent; /* For otherrels, this is the RT index of * inheritance table mentioned in the query * from which this relation originated */ + /* + * Set only if this is an inheritance parent relation. This information + * is needed when initializing the planning info for children. + */ + TupleDesc tupdesc; /* A "copy" of the table's tuple desriptor */ + Oid reltype; /* Table's reltype */ } RelOptInfo; /* diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 81abcf53a8..b1baa3117a 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -261,6 +261,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index 7d53cbbb87..edaf2a3b4f 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -26,7 +26,7 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); + bool inhparent, Bitmapset *updatedCols, RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 38608770a2..faae07d240 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -38,7 +38,8 @@ extern Expr *canonicalize_qual(Expr *qual, bool is_check); /* * prototypes for preptlist.c */ -extern List *preprocess_targetlist(PlannerInfo *root); +extern List *preprocess_targetlist(PlannerInfo *root, + bool inheritance_expanded); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); @@ -51,7 +52,10 @@ extern void expand_inherited_tables(PlannerInfo *root); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); - +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parentrel, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex); extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, Relids child_relids, Relids top_parent_relids); @@ -64,5 +68,4 @@ extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, Relids left_relids, Relids right_relids); extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, Relids child_relids, Relids top_parent_relids); - #endif /* PREP_H */ diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h index e07aaaf798..ac0a979010 100644 --- a/src/include/partitioning/partprune.h +++ b/src/include/partitioning/partprune.h @@ -76,7 +76,7 @@ extern PartitionPruneInfo *make_partition_pruneinfo(PlannerInfo *root, List *subpaths, List *partitioned_rels, List *prunequal); -extern Relids prune_append_rel_partitions(RelOptInfo *rel); +extern Bitmapset *prune_append_rel_partitions(RelOptInfo *rel); extern Bitmapset *get_matching_partitions(PartitionPruneContext *context, List *pruning_steps); diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 6bc106831e..1450cef057 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) -- 2.11.0
v7-0004-Move-append-expansion-code-into-its-own-file.patch (text/plain, 112.3K) download From ceaab25e8faba6f6c61c0612e3037e4a47c643e1 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 7 Nov 2018 16:51:31 +0900 Subject: [PATCH v7 4/6] Move append expansion code into its own file This commit moves expand_append_rel and underlings currently in optimizer/prep/prepunionc.c to optimizer/utils/append.c. All of the AppendRelInfo based expression manipulation routines are moved to optimizer/utils/appendinfo.c. This commit only moves the code and contains no functional changes. --- src/backend/optimizer/path/allpaths.c | 2 + src/backend/optimizer/path/equivclass.c | 1 + src/backend/optimizer/path/joinrels.c | 1 + src/backend/optimizer/plan/planner.c | 1 + src/backend/optimizer/prep/prepunion.c | 1574 ------------------------------- src/backend/optimizer/util/Makefile | 5 +- src/backend/optimizer/util/append.c | 777 +++++++++++++++ src/backend/optimizer/util/appendinfo.c | 851 +++++++++++++++++ src/backend/optimizer/util/pathnode.c | 1 + src/backend/optimizer/util/relnode.c | 1 + src/backend/partitioning/partprune.c | 1 + src/include/optimizer/append.h | 25 + src/include/optimizer/appendinfo.h | 43 + src/include/optimizer/prep.h | 19 - 14 files changed, 1707 insertions(+), 1595 deletions(-) create mode 100644 src/backend/optimizer/util/append.c create mode 100644 src/backend/optimizer/util/appendinfo.c create mode 100644 src/include/optimizer/append.h create mode 100644 src/include/optimizer/appendinfo.h diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index a4dd34f2cd..d93883ed42 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -30,6 +30,8 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "optimizer/append.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/geqo.h" diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index b22b36ec0e..5ace28eab0 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -22,6 +22,7 @@ #include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index da0831de4e..6e321ec9e7 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -15,6 +15,7 @@ #include "postgres.h" #include "miscadmin.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 5747694f63..3513b25cac 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -39,6 +39,7 @@ #include "nodes/print.h" #endif #include "nodes/relation.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index e4d4e7d6dd..70f37c593f 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -12,11 +12,6 @@ * case, but most of the heavy lifting for that is done elsewhere, * notably in prepjointree.c and allpaths.c. * - * There is also some code here to support planning of queries that use - * inheritance (SELECT FROM foo*). Inheritance trees are converted into - * append relations, and thenceforth share code with the UNION ALL case. - * - * * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -39,32 +34,21 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" -#include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" -#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" -#include "optimizer/var.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/selfuncs.h" #include "utils/syscache.h" -typedef struct -{ - PlannerInfo *root; - int nappinfos; - AppendRelInfo **appinfos; -} adjust_appendrel_attrs_context; - static RelOptInfo *recurse_set_operations(Node *setOp, PlannerInfo *root, List *colTypes, List *colCollations, bool junkOK, @@ -103,34 +87,6 @@ static List *generate_append_tlist(List *colTypes, List *colCollations, List *input_tlists, List *refnames_tlist); static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist); -static void expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, - RangeTblEntry *rte, Index rti); -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti, RelOptInfo *rel); -static void expand_partitioned_rtentry(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel); -static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel, - PlanRowMark *top_parentrc, Relation childrel, - RangeTblEntry **childrte_p, Index *childRTindex_p); -static void make_inh_translation_list(TupleDesc old_tupdesc, - TupleDesc new_tupdesc, - Oid from_rel, Oid to_rel, - Index newvarno, List **translated_vars); -static RelOptInfo *build_append_child_rel(PlannerInfo *root, - RelOptInfo *parent, - Index childRTindex); -static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); -static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars); -static Node *adjust_appendrel_attrs_mutator(Node *node, - adjust_appendrel_attrs_context *context); -static Relids adjust_child_relids(Relids relids, int nappinfos, - AppendRelInfo **appinfos); -static List *adjust_inherited_tlist(List *tlist, - AppendRelInfo *context); /* @@ -1467,1533 +1423,3 @@ generate_setop_grouplist(SetOperationStmt *op, List *targetlist) Assert(lg == NULL); return grouplist; } - - -/* - * expand_inherited_tables - * Expand each rangetable entry that represents an inheritance set - * into an "append relation". At the conclusion of this process, - * the "inh" flag is set in all and only those RTEs that are append - * relation parents. - */ -void -expand_inherited_tables(PlannerInfo *root) -{ - int orig_rtable_size; - Index rti; - - Assert(root->simple_rel_array_size > 0); - orig_rtable_size = root->simple_rel_array_size; - - /* - * expand_append_rtentry may add RTEs to parse->rtable. The function is - * expected to recursively handle any RTEs that it creates with inh=true. - * So just scan as far as the original end of the rtable list. - */ - for (rti = 1; rti < orig_rtable_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - RangeTblEntry *rte = root->simple_rte_array[rti]; - - /* there may be empty slots corresponding to non-baserel RTEs */ - if (brel == NULL) - continue; - - if (rte->inh) - expand_append_rtentry(root, brel, rte, rti); - } -} - -/* - * expand_append_rtentry - * This initializes RelOptInfos for an appendrel's child relations, if - * any - * - * 'rel' is the appendrel parent, whose range table entry ('rte') has been - * marked to require adding children. An appendrel parent could either - * be a subquery (if we flattened UNION ALL query) or a table that's known - * to have inheritance children. The latter consists of both regular - * inheritance parents and partitioned tables. - * - * For a subquery parent, there is not much to be done here because the - * children's RTEs are already present in the query, so we just initialize - * RelOptInfos for them. Also, the AppendRelInfos for child subqueries - * have already been added. - * - * For tables, we need to add the children to the range table and initialize - * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For - * a partitioned parent, we only add the children remaining after pruning. - * For regular inheritance parents, we find the children using - * find_all_inheritors and add all of them. - * - * If it turns out that there are no children, then we set rte->inh to false - * to let the caller know that only the parent table needs to be scanned. The - * caller can accordingly switch to a non-Append path. For a partitioned - * parent, that means an empty relation because parents themselves contain no - * data. - * - * For the regular inheritance case, the parent also gets another RTE with - * inh = false to represent it as an appendrel child. The original RTE is - * considered to represent the whole inheritance set. - */ -static void -expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, - Index rti) -{ - Assert(rte->inh); - /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ - Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); - - /* - * UNION ALL children already got RTEs and AppendRelInfos, so just build - * RelOptInfos and return. - * - * It might be a bit odd that this code is in this, because there is - * nothing to expand really. - */ - if (rte->rtekind == RTE_SUBQUERY) - { - ListCell *l; - - /* - * We don't need to use expand_planner_arrays in this case, because - * no new child RTEs are created. setup_simple_rel_arrays() and - * setup_append_rel_array would've considered these child RTEs when - * allocating space for various arrays. - */ - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst(l); - Index childRTindex = appinfo->child_relid; - - if (appinfo->parent_relid != rti) - continue; - - Assert(childRTindex < root->simple_rel_array_size); - Assert(root->simple_rte_array[childRTindex] != NULL); - - /* - * We set the correct value of baserestricinfo and - * baserestrict_min_security below. - */ - root->simple_rel_array[childRTindex] = - build_append_child_rel(root, rel, appinfo->child_relid); - } - } - else - { - Assert(rte->rtekind == RTE_RELATION); - Assert(has_subclass(rte->relid)); - - /* - * The rewriter should already have obtained an appropriate lock on - * each relation named in the query. However, for each child relation - * we add to the query, we must obtain an appropriate lock, because - * this will be the first use of those relations in the - * parse/rewrite/plan pipeline. Child rels should use the same - * lockmode as their parent. - */ - Assert(rte->rellockmode != NoLock); - - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - expand_partitioned_rtentry(root, rte, rti, rel); - else - expand_inherited_rtentry(root, rte, rti, rel); - } -} - -/* - * expand_inherited_rtentry - * Add entries for all the child tables to the query's rangetable, and - * build AppendRelInfo nodes for all the child tables and add them to - * root->append_rel_list. - * - * Note that the original RTE is considered to represent the whole - * inheritance set. The first of the generated RTEs is an RTE for the same - * table, but with inh = false, to represent the parent table in its role - * as a simple member of the inheritance set. - * - * A childless table is never considered to be an inheritance set. For - * regular inheritance, a parent RTE must always have at least two associated - * AppendRelInfos: one corresponding to the parent table as a simple member of - * inheritance set and one or more corresponding to the actual children. - */ -static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, - RelOptInfo *rel) -{ - Oid parentOID; - PlanRowMark *oldrc; - LOCKMODE lockmode = rte->rellockmode; - List *inhOIDs; - ListCell *l; - int num_children; - int num_children_added = 0; - - Assert(rte->rtekind == RTE_RELATION); - Assert(lockmode != NoLock); - parentOID = rte->relid; - - /* Scan for all members of inheritance set, acquire needed locks */ - inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); - - /* - * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite above has_subclass() check, if table - * once had a child but no longer does. - */ - num_children = list_length(inhOIDs); - if (num_children < 2) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks - * should've set isParent = true. We'll generate a new PlanRowMark for - * each child. - */ - oldrc = get_plan_rowmark(root->rowMarks, rti); - Assert(oldrc == NULL || oldrc->isParent); - - /* - * Must expand PlannerInfo arrays by num_children before we can add - * children. - */ - expand_planner_arrays(root, num_children); - - foreach(l, inhOIDs) - { - Oid childOID = lfirst_oid(l); - Relation newrelation; - RangeTblEntry *childrte; - Index childRTindex; - - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (RELATION_IS_OTHER_TEMP(newrelation)) - { - heap_close(newrelation, lockmode); - continue; - } - - (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, - newrelation, &childrte, - &childRTindex); - Assert(childrte != NULL); - /* All regular inheritance children are leaf children. */ - Assert(!childrte->inh); - Assert(childRTindex > 0); - - /* Close child relations, but keep locks */ - heap_close(newrelation, NoLock); - num_children_added++; - } - - /* - * If all children, including the parent (as child rel), were - * excluded, mark the parent rel as empty. If all the children were temp - * tables, pretend it's a non-inheritance situation; we don't need Append - * node in that case. The duplicate RTE we added for the parent table is - * harmless, so we don't bother to get rid of it; ditto for the useless - * PlanRowMark node. - */ - if (num_children_added == 0) - mark_dummy_rel(rel); - else if (num_children_added == 1) - rte->inh = false; - - /* - * Add junk columns needed by the row mark if any and also add the - * relevant expressions to the root parent's reltarget. - */ - if (oldrc) - { - List *tlist = add_rowmark_junk_columns(root, oldrc); - - build_base_rel_tlists(root, tlist); - } -} - -/* - * expand_partitioned_rtentry - * Prunes unnecessary partitions of a partitioned table and adds - * remaining ones to the Query and the PlannerInfo - * - * Partitions are added to the query in order in which they are found in - * the parent's PartitionDesc. - * - * Note: even though only the unpruned partitions will be added to the - * resulting plan, this still locks *all* partitions via find_all_inheritors - * in order to avoid partitions being locked in a different order than other - * places in the backend that may lock partitions. - */ -static void -expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel) -{ - LOCKMODE lockmode = parentrte->rellockmode; - PlanRowMark *rootrc = NULL; - int i; - Bitmapset *partindexes; - Index rootParentRTindex = parentrel->inh_root_parent > 0 ? - parentrel->inh_root_parent : - parentRTindex; - - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - - /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, values of the - * indexes of partitioned relations that appear down in the tree will be - * bubbled up into root parent's list so that when we've created Paths for - * all the children, the root table's list will contain all such indexes. - */ - parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - - /* Perform pruning. */ - partindexes = prune_append_rel_partitions(parentrel); - - /* Must expand PlannerInfo arrays before we can add children. */ - expand_planner_arrays(root, bms_num_members(partindexes)); - - /* - * For partitioned tables, we also store the partition RelOptInfo - * pointers in the parent's RelOptInfo. - */ - parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * - parentrel->nparts); - - rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); - Assert(rootrc == NULL || rootrc->isParent); - i = -1; - while ((i = bms_next_member(partindexes, i)) >= 0) - { - Oid childOID = parentrel->part_oids[i]; - Relation newrelation; - RelOptInfo *childrel; - RangeTblEntry *childrte; - Index childRTindex; - - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); - Assert(!RELATION_IS_OTHER_TEMP(newrelation)); - - /* - * A partitioned child table with 0 children is a dummy rel, so don't - * bother creating planner objects for it. - */ - if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && - RelationGetPartitionDesc(newrelation)->nparts == 0) - { - heap_close(newrelation, NoLock); - continue; - } - - childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, - parentrel, rootrc, newrelation, - &childrte, &childRTindex); - Assert(childrel != NULL); - parentrel->part_rels[i] = childrel; - - /* Close child relations, but keep locks */ - heap_close(newrelation, NoLock); - - /* If the child is partitioned itself, expand it too. */ - if (childrel->part_scheme) - { - Assert(childrte->inh); - expand_partitioned_rtentry(root, childrte, childRTindex, - childrel); - } - } - - /* - * Add junk columns needed by the row mark if any and also add the - * relevant expressions to the root parent's reltarget. - */ - if (rootrc) - { - List *tlist = add_rowmark_junk_columns(root, rootrc); - - build_base_rel_tlists(root, tlist); - } -} - -/* - * add_inheritance_child_rel - * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally - * a RelOptInfo for an inheritance child relation. - * - * The return value is the RelOptInfo that's added. - * - * PlanRowMarks still carry the top-parent's RTI, and the top-parent's - * allMarkTypes field still accumulates values from all descendents. - * - * "parentrte" and "parentRTindex" are immediate parent's RTE and - * RTI. "top_parentrc" is top parent's PlanRowMark. - */ -static RelOptInfo * -add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel, - PlanRowMark *top_parentrc, Relation childrel, - RangeTblEntry **childrte_p, Index *childRTindex_p) -{ - Query *parse = root->parse; - Oid childOID = RelationGetRelid(childrel); - RangeTblEntry *childrte; - Index childRTindex; - AppendRelInfo *appinfo; - RelOptInfo *childrelopt; - - /* - * Build an RTE for the child, and attach to query's rangetable list. We - * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh appropriately. Also, set requiredPerms to zero - * since all required permissions checks are done on the original RTE. - * Likewise, set the child's securityQuals to empty, because we only want - * to apply the parent's RLS conditions regardless of what RLS properties - * individual children may have. (This is an intentional choice to make - * inherited RLS work like regular permissions checks.) The parent - * securityQuals will be propagated to children along with other base - * restriction clauses, so we don't need to do it here. - */ - childrte = copyObject(parentrte); - *childrte_p = childrte; - childrte->relid = childOID; - childrte->relkind = childrel->rd_rel->relkind; - /* - * A partitioned child will need to be expanded as an append parent - * itself, so set its inh to true. - */ - childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); - childrte->requiredPerms = 0; - childrte->securityQuals = NIL; - parse->rtable = lappend(parse->rtable, childrte); - childRTindex = list_length(parse->rtable); - *childRTindex_p = childRTindex; - - /* Create an AppendRelInfo and add it to planner's global list. */ - appinfo = make_append_rel_info(parentrel, parentrte, - childrel->rd_att, - childOID, - childrel->rd_rel->reltype, - childRTindex); - root->append_rel_list = lappend(root->append_rel_list, appinfo); - - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childrte->relid != parentrte->relid) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } - - /* - * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE. - */ - if (top_parentrc) - { - PlanRowMark *childrc = makeNode(PlanRowMark); - - childrc->rti = childRTindex; - childrc->prti = top_parentrc->rti; - childrc->rowmarkId = top_parentrc->rowmarkId; - /* Reselect rowmark type, because relkind might not match parent */ - childrc->markType = select_rowmark_type(childrte, - top_parentrc->strength); - childrc->allMarkTypes = (1 << childrc->markType); - childrc->strength = top_parentrc->strength; - childrc->waitPolicy = top_parentrc->waitPolicy; - - /* - * We mark RowMarks for partitioned child tables as parent RowMarks so - * that the executor ignores them (except their existence means that - * the child tables be locked using appropriate mode). - */ - childrc->isParent = (childrte->relkind == RELKIND_PARTITIONED_TABLE); - - /* Include child's rowmark type in top parent's allMarkTypes */ - top_parentrc->allMarkTypes |= childrc->allMarkTypes; - - root->rowMarks = lappend(root->rowMarks, childrc); - } - - /* - * Add the RelOptInfo. Even though we may not really scan this relation - * for reasons such as contradictory quals, we still need need to create - * one, because for every RTE in the query's range table, there must be an - * accompanying RelOptInfo. - */ - - /* First, store the RTE and appinfos into planner arrays. */ - Assert(root->simple_rte_array[childRTindex] == NULL); - root->simple_rte_array[childRTindex] = childrte; - Assert(root->append_rel_array[childRTindex] == NULL); - root->append_rel_array[childRTindex] = appinfo; - - childrelopt = build_append_child_rel(root, parentrel, childRTindex); - Assert(childrelopt != NULL); - - return childrelopt; -} - -/* - * make_inh_translation_list - * Build the list of translations from parent Vars to child Vars for - * an inheritance child. - * - * For paranoia's sake, we match type/collation as well as attribute name. - */ -static void -make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, - Oid from_rel, Oid to_rel, - Index newvarno, List **translated_vars) -{ - List *vars = NIL; - int oldnatts = old_tupdesc->natts; - int newnatts = new_tupdesc->natts; - int old_attno; - int new_attno = 0; - - for (old_attno = 0; old_attno < oldnatts; old_attno++) - { - Form_pg_attribute att; - char *attname; - Oid atttypid; - int32 atttypmod; - Oid attcollation; - - att = TupleDescAttr(old_tupdesc, old_attno); - if (att->attisdropped) - { - /* Just put NULL into this list entry */ - vars = lappend(vars, NULL); - continue; - } - attname = NameStr(att->attname); - atttypid = att->atttypid; - atttypmod = att->atttypmod; - attcollation = att->attcollation; - - /* - * When we are generating the "translation list" for the parent table - * of an inheritance set, no need to search for matches. - */ - if (from_rel == to_rel) - { - vars = lappend(vars, makeVar(newvarno, - (AttrNumber) (old_attno + 1), - atttypid, - atttypmod, - attcollation, - 0)); - continue; - } - - /* - * Otherwise we have to search for the matching column by name. - * There's no guarantee it'll have the same column position, because - * of cases like ALTER TABLE ADD COLUMN and multiple inheritance. - * However, in simple cases, the relative order of columns is mostly - * the same in both relations, so try the column of newrelation that - * follows immediately after the one that we just found, and if that - * fails, let syscache handle it. - */ - if (new_attno >= newnatts || - (att = TupleDescAttr(new_tupdesc, new_attno))->attisdropped || - strcmp(attname, NameStr(att->attname)) != 0) - { - HeapTuple newtup; - - newtup = SearchSysCacheAttName(to_rel, attname); - if (!newtup) - elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, get_rel_name(to_rel)); - new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; - ReleaseSysCache(newtup); - - att = TupleDescAttr(new_tupdesc, new_attno); - } - - /* Found it, check type and collation match */ - if (atttypid != att->atttypid || atttypmod != att->atttypmod) - elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, get_rel_name(to_rel)); - if (attcollation != att->attcollation) - elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, get_rel_name(to_rel)); - - vars = lappend(vars, makeVar(newvarno, - (AttrNumber) (new_attno + 1), - atttypid, - atttypmod, - attcollation, - 0)); - new_attno++; - } - - *translated_vars = vars; -} - -/* - * translate_col_privs - * Translate a bitmapset representing per-column privileges from the - * parent rel's attribute numbering to the child's. - * - * The only surprise here is that we don't translate a parent whole-row - * reference into a child whole-row reference. That would mean requiring - * permissions on all child columns, which is overly strict, since the - * query is really only going to reference the inherited columns. Instead - * we set the per-column bits for all inherited columns. - */ -static Bitmapset * -translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars) -{ - Bitmapset *child_privs = NULL; - bool whole_row; - int attno; - ListCell *lc; - - /* System attributes have the same numbers in all tables */ - for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) - { - if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - attno - FirstLowInvalidHeapAttributeNumber); - } - - /* Check if parent has whole-row reference */ - whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, - parent_privs); - - /* And now translate the regular user attributes, using the vars list */ - attno = InvalidAttrNumber; - foreach(lc, translated_vars) - { - Var *var = lfirst_node(Var, lc); - - attno++; - if (var == NULL) /* ignore dropped columns */ - continue; - if (whole_row || - bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - var->varattno - FirstLowInvalidHeapAttributeNumber); - } - - return child_privs; -} - -/* - * build_append_child_rel - * Build a RelOptInfo for child relation of an append rel - * - * After creating the RelOptInfo for the given child RT index, it goes on to - * initialize some of its fields base on the parent RelOptInfo. - * - * If the quals in baserestrictinfo turns out to be self-contradictory, the - * RelOptInfo is marked dummy before returning. - */ -static RelOptInfo * -build_append_child_rel(PlannerInfo *root, - RelOptInfo *parent, - Index childRTindex) -{ - RelOptInfo *childrel; - RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; - AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; - List *childquals; - ListCell *lc; - bool have_const_false_cq; - Index cq_min_security; - - /* Build the RelOptInfo. */ - childrel = build_simple_rel(root, childRTindex, parent); - - /* - * Propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - */ - childrel->direct_lateral_relids = parent->direct_lateral_relids; - childrel->lateral_relids = parent->lateral_relids; - childrel->lateral_referencers = parent->lateral_referencers; - - /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) - */ - childquals = false; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, parent->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual = (Node *) rinfo->clause; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, childqual, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might - * have securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals.) Pull any such securityQuals up into the - * baserestrictinfo for the child. This is similar to - * process_security_barrier_quals() for the parent rel, except - * that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* - * not likely that we'd see constants here, so no - * check - */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, - false, - false, - security_level, - NULL, NULL, - NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* Set child's version of baserestrictinfo. */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - } - - return childrel; -} - -/* - * add_rowmark_junk_columns - * Add necessary junk columns for rowmarked inheritance parent rel. - * - * These values are needed for locking of rels selected FOR UPDATE/SHARE, and - * to do EvalPlanQual rechecking. See comments for PlanRowMark in - * plannodes.h. - */ -static List * -add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) -{ - List *tlist = root->processed_tlist; - Var *var; - char resname[32]; - TargetEntry *tle; - - if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) - { - /* Need to fetch TID */ - var = makeVar(rc->rti, - SelfItemPointerAttributeNumber, - TIDOID, - -1, - InvalidOid, - 0); - snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - } - if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) - { - /* Need the whole row as a junk var */ - var = makeWholeRowVar(root->simple_rte_array[rc->rti], - rc->rti, - 0, - false); - snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - } - - /* For inheritance cases, always fetch the tableoid too. */ - var = makeVar(rc->rti, - TableOidAttributeNumber, - OIDOID, - -1, - InvalidOid, - 0); - snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - - return tlist; -} - -AppendRelInfo * -make_append_rel_info(RelOptInfo *parentrel, RangeTblEntry *parentrte, - TupleDesc childdesc, Oid childoid, Oid childreltype, - Index childRTindex) -{ - AppendRelInfo *appinfo = makeNode(AppendRelInfo); - - appinfo->parent_relid = parentrel->relid; - appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->reltype; - appinfo->child_reltype = childreltype; - make_inh_translation_list(parentrel->tupdesc, childdesc, - parentrte->relid, childoid, - childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = parentrte->relid; - - return appinfo; -} - -/* - * adjust_appendrel_attrs - * Copy the specified query or expression and translate Vars referring to a - * parent rel to refer to the corresponding child rel instead. We also - * update rtindexes appearing outside Vars, such as resultRelation and - * jointree relids. - * - * Note: this is only applied after conversion of sublinks to subplans, - * so we don't need to cope with recursion into sub-queries. - * - * Note: this is not hugely different from what pullup_replace_vars() does; - * maybe we should try to fold the two routines together. - */ -Node * -adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, - AppendRelInfo **appinfos) -{ - Node *result; - adjust_appendrel_attrs_context context; - - context.root = root; - context.nappinfos = nappinfos; - context.appinfos = appinfos; - - /* If there's nothing to adjust, don't call this function. */ - Assert(nappinfos >= 1 && appinfos != NULL); - - /* - * Must be prepared to start with a Query or a bare expression tree. - */ - if (node && IsA(node, Query)) - { - Query *newnode; - int cnt; - - newnode = query_tree_mutator((Query *) node, - adjust_appendrel_attrs_mutator, - (void *) &context, - QTW_IGNORE_RC_SUBQUERIES); - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (newnode->resultRelation == appinfo->parent_relid) - { - newnode->resultRelation = appinfo->child_relid; - /* Fix tlist resnos too, if it's inherited UPDATE */ - if (newnode->commandType == CMD_UPDATE) - newnode->targetList = - adjust_inherited_tlist(newnode->targetList, - appinfo); - break; - } - } - - result = (Node *) newnode; - } - else - result = adjust_appendrel_attrs_mutator(node, &context); - - return result; -} - -static Node * -adjust_appendrel_attrs_mutator(Node *node, - adjust_appendrel_attrs_context *context) -{ - AppendRelInfo **appinfos = context->appinfos; - int nappinfos = context->nappinfos; - int cnt; - - if (node == NULL) - return NULL; - if (IsA(node, Var)) - { - Var *var = (Var *) copyObject(node); - AppendRelInfo *appinfo = NULL; - - for (cnt = 0; cnt < nappinfos; cnt++) - { - if (var->varno == appinfos[cnt]->parent_relid) - { - appinfo = appinfos[cnt]; - break; - } - } - - if (var->varlevelsup == 0 && appinfo) - { - var->varno = appinfo->child_relid; - var->varnoold = appinfo->child_relid; - if (var->varattno > 0) - { - Node *newnode; - - if (var->varattno > list_length(appinfo->translated_vars)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - var->varattno, get_rel_name(appinfo->parent_reloid)); - newnode = copyObject(list_nth(appinfo->translated_vars, - var->varattno - 1)); - if (newnode == NULL) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - var->varattno, get_rel_name(appinfo->parent_reloid)); - return newnode; - } - else if (var->varattno == 0) - { - /* - * Whole-row Var: if we are dealing with named rowtypes, we - * can use a whole-row Var for the child table plus a coercion - * step to convert the tuple layout to the parent's rowtype. - * Otherwise we have to generate a RowExpr. - */ - if (OidIsValid(appinfo->child_reltype)) - { - Assert(var->vartype == appinfo->parent_reltype); - if (appinfo->parent_reltype != appinfo->child_reltype) - { - ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr); - - r->arg = (Expr *) var; - r->resulttype = appinfo->parent_reltype; - r->convertformat = COERCE_IMPLICIT_CAST; - r->location = -1; - /* Make sure the Var node has the right type ID, too */ - var->vartype = appinfo->child_reltype; - return (Node *) r; - } - } - else - { - /* - * Build a RowExpr containing the translated variables. - * - * In practice var->vartype will always be RECORDOID here, - * so we need to come up with some suitable column names. - * We use the parent RTE's column names. - * - * Note: we can't get here for inheritance cases, so there - * is no need to worry that translated_vars might contain - * some dummy NULLs. - */ - RowExpr *rowexpr; - List *fields; - RangeTblEntry *rte; - - rte = rt_fetch(appinfo->parent_relid, - context->root->parse->rtable); - fields = copyObject(appinfo->translated_vars); - rowexpr = makeNode(RowExpr); - rowexpr->args = fields; - rowexpr->row_typeid = var->vartype; - rowexpr->row_format = COERCE_IMPLICIT_CAST; - rowexpr->colnames = copyObject(rte->eref->colnames); - rowexpr->location = -1; - - return (Node *) rowexpr; - } - } - /* system attributes don't need any other translation */ - } - return (Node *) var; - } - if (IsA(node, CurrentOfExpr)) - { - CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node); - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (cexpr->cvarno == appinfo->parent_relid) - { - cexpr->cvarno = appinfo->child_relid; - break; - } - } - return (Node *) cexpr; - } - if (IsA(node, RangeTblRef)) - { - RangeTblRef *rtr = (RangeTblRef *) copyObject(node); - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (rtr->rtindex == appinfo->parent_relid) - { - rtr->rtindex = appinfo->child_relid; - break; - } - } - return (Node *) rtr; - } - if (IsA(node, JoinExpr)) - { - /* Copy the JoinExpr node with correct mutation of subnodes */ - JoinExpr *j; - AppendRelInfo *appinfo; - - j = (JoinExpr *) expression_tree_mutator(node, - adjust_appendrel_attrs_mutator, - (void *) context); - /* now fix JoinExpr's rtindex (probably never happens) */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - appinfo = appinfos[cnt]; - - if (j->rtindex == appinfo->parent_relid) - { - j->rtindex = appinfo->child_relid; - break; - } - } - return (Node *) j; - } - if (IsA(node, PlaceHolderVar)) - { - /* Copy the PlaceHolderVar node with correct mutation of subnodes */ - PlaceHolderVar *phv; - - phv = (PlaceHolderVar *) expression_tree_mutator(node, - adjust_appendrel_attrs_mutator, - (void *) context); - /* now fix PlaceHolderVar's relid sets */ - if (phv->phlevelsup == 0) - phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos, - context->appinfos); - return (Node *) phv; - } - - /* - * This is needed, because inheritance_make_rel_from_joinlist needs to - * translate root->join_info_list executing make_rel_from_joinlist for a - * given child. - */ - if (IsA(node, SpecialJoinInfo)) - { - SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; - SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); - - memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); - newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, - context->nappinfos, - context->appinfos); - newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, - context->nappinfos, - context->appinfos); - newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, - context->nappinfos, - context->appinfos); - newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, - context->nappinfos, - context->appinfos); - newinfo->semi_rhs_exprs = - (List *) expression_tree_mutator((Node *) - oldinfo->semi_rhs_exprs, - adjust_appendrel_attrs_mutator, - (void *) context); - return (Node *) newinfo; - } - - /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, AppendRelInfo)); - Assert(!IsA(node, PlaceHolderInfo)); - Assert(!IsA(node, MinMaxAggInfo)); - - /* - * We have to process RestrictInfo nodes specially. (Note: although - * set_append_rel_pathlist will hide RestrictInfos in the parent's - * baserestrictinfo list from us, it doesn't hide those in joininfo.) - */ - if (IsA(node, RestrictInfo)) - { - RestrictInfo *oldinfo = (RestrictInfo *) node; - RestrictInfo *newinfo = makeNode(RestrictInfo); - - /* Copy all flat-copiable fields */ - memcpy(newinfo, oldinfo, sizeof(RestrictInfo)); - - /* Recursively fix the clause itself */ - newinfo->clause = (Expr *) - adjust_appendrel_attrs_mutator((Node *) oldinfo->clause, context); - - /* and the modified version, if an OR clause */ - newinfo->orclause = (Expr *) - adjust_appendrel_attrs_mutator((Node *) oldinfo->orclause, context); - - /* adjust relid sets too */ - newinfo->clause_relids = adjust_child_relids(oldinfo->clause_relids, - context->nappinfos, - context->appinfos); - newinfo->required_relids = adjust_child_relids(oldinfo->required_relids, - context->nappinfos, - context->appinfos); - newinfo->outer_relids = adjust_child_relids(oldinfo->outer_relids, - context->nappinfos, - context->appinfos); - newinfo->nullable_relids = adjust_child_relids(oldinfo->nullable_relids, - context->nappinfos, - context->appinfos); - newinfo->left_relids = adjust_child_relids(oldinfo->left_relids, - context->nappinfos, - context->appinfos); - newinfo->right_relids = adjust_child_relids(oldinfo->right_relids, - context->nappinfos, - context->appinfos); - - /* - * Reset cached derivative fields, since these might need to have - * different values when considering the child relation. Note we - * don't reset left_ec/right_ec: each child variable is implicitly - * equivalent to its parent, so still a member of the same EC if any. - */ - newinfo->eval_cost.startup = -1; - newinfo->norm_selec = -1; - newinfo->outer_selec = -1; - newinfo->left_em = NULL; - newinfo->right_em = NULL; - newinfo->scansel_cache = NIL; - newinfo->left_bucketsize = -1; - newinfo->right_bucketsize = -1; - newinfo->left_mcvfreq = -1; - newinfo->right_mcvfreq = -1; - - return (Node *) newinfo; - } - - /* - * NOTE: we do not need to recurse into sublinks, because they should - * already have been converted to subplans before we see them. - */ - Assert(!IsA(node, SubLink)); - Assert(!IsA(node, Query)); - - return expression_tree_mutator(node, adjust_appendrel_attrs_mutator, - (void *) context); -} - -/* - * Substitute child relids for parent relids in a Relid set. The array of - * appinfos specifies the substitutions to be performed. - */ -static Relids -adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos) -{ - Bitmapset *result = NULL; - int cnt; - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - /* Remove parent, add child */ - if (bms_is_member(appinfo->parent_relid, relids)) - { - /* Make a copy if we are changing the set. */ - if (!result) - result = bms_copy(relids); - - result = bms_del_member(result, appinfo->parent_relid); - result = bms_add_member(result, appinfo->child_relid); - } - } - - /* If we made any changes, return the modified copy. */ - if (result) - return result; - - /* Otherwise, return the original set without modification. */ - return relids; -} - -/* - * Replace any relid present in top_parent_relids with its child in - * child_relids. Members of child_relids can be multiple levels below top - * parent in the partition hierarchy. - */ -Relids -adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, - Relids child_relids, Relids top_parent_relids) -{ - AppendRelInfo **appinfos; - int nappinfos; - Relids parent_relids = NULL; - Relids result; - Relids tmp_result = NULL; - int cnt; - - /* - * If the given relids set doesn't contain any of the top parent relids, - * it will remain unchanged. - */ - if (!bms_overlap(relids, top_parent_relids)) - return relids; - - appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); - - /* Construct relids set for the immediate parent of the given child. */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); - } - - /* Recurse if immediate parent is not the top parent. */ - if (!bms_equal(parent_relids, top_parent_relids)) - { - tmp_result = adjust_child_relids_multilevel(root, relids, - parent_relids, - top_parent_relids); - relids = tmp_result; - } - - result = adjust_child_relids(relids, nappinfos, appinfos); - - /* Free memory consumed by any intermediate result. */ - if (tmp_result) - bms_free(tmp_result); - bms_free(parent_relids); - pfree(appinfos); - - return result; -} - -/* - * Adjust the targetlist entries of an inherited UPDATE operation - * - * The expressions have already been fixed, but we have to make sure that - * the target resnos match the child table (they may not, in the case of - * a column that was added after-the-fact by ALTER TABLE). In some cases - * this can force us to re-order the tlist to preserve resno ordering. - * (We do all this work in special cases so that preptlist.c is fast for - * the typical case.) - * - * The given tlist has already been through expression_tree_mutator; - * therefore the TargetEntry nodes are fresh copies that it's okay to - * scribble on. - * - * Note that this is not needed for INSERT because INSERT isn't inheritable. - */ -static List * -adjust_inherited_tlist(List *tlist, AppendRelInfo *context) -{ - bool changed_it = false; - ListCell *tl; - List *new_tlist; - bool more; - int attrno; - - /* This should only happen for an inheritance case, not UNION ALL */ - Assert(OidIsValid(context->parent_reloid)); - - /* Scan tlist and update resnos to match attnums of child rel */ - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - Var *childvar; - - if (tle->resjunk) - continue; /* ignore junk items */ - - /* Look up the translation of this column: it must be a Var */ - if (tle->resno <= 0 || - tle->resno > list_length(context->translated_vars)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - tle->resno, get_rel_name(context->parent_reloid)); - childvar = (Var *) list_nth(context->translated_vars, tle->resno - 1); - if (childvar == NULL || !IsA(childvar, Var)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - tle->resno, get_rel_name(context->parent_reloid)); - - if (tle->resno != childvar->varattno) - { - tle->resno = childvar->varattno; - changed_it = true; - } - } - - /* - * If we changed anything, re-sort the tlist by resno, and make sure - * resjunk entries have resnos above the last real resno. The sort - * algorithm is a bit stupid, but for such a seldom-taken path, small is - * probably better than fast. - */ - if (!changed_it) - return tlist; - - new_tlist = NIL; - more = true; - for (attrno = 1; more; attrno++) - { - more = false; - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - - if (tle->resjunk) - continue; /* ignore junk items */ - - if (tle->resno == attrno) - new_tlist = lappend(new_tlist, tle); - else if (tle->resno > attrno) - more = true; - } - } - - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - - if (!tle->resjunk) - continue; /* here, ignore non-junk items */ - - tle->resno = attrno; - new_tlist = lappend(new_tlist, tle); - attrno++; - } - - return new_tlist; -} - -/* - * adjust_appendrel_attrs_multilevel - * Apply Var translations from a toplevel appendrel parent down to a child. - * - * In some cases we need to translate expressions referencing a parent relation - * to reference an appendrel child that's multiple levels removed from it. - */ -Node * -adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, - Relids child_relids, - Relids top_parent_relids) -{ - AppendRelInfo **appinfos; - Bitmapset *parent_relids = NULL; - int nappinfos; - int cnt; - - Assert(bms_num_members(child_relids) == bms_num_members(top_parent_relids)); - - appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); - - /* Construct relids set for the immediate parent of given child. */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); - } - - /* Recurse if immediate parent is not the top parent. */ - if (!bms_equal(parent_relids, top_parent_relids)) - node = adjust_appendrel_attrs_multilevel(root, node, parent_relids, - top_parent_relids); - - /* Now translate for this child */ - node = adjust_appendrel_attrs(root, node, nappinfos, appinfos); - - pfree(appinfos); - - return node; -} - -/* - * Construct the SpecialJoinInfo for a child-join by translating - * SpecialJoinInfo for the join between parents. left_relids and right_relids - * are the relids of left and right side of the join respectively. - */ -SpecialJoinInfo * -build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, - Relids left_relids, Relids right_relids) -{ - SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo); - AppendRelInfo **left_appinfos; - int left_nappinfos; - AppendRelInfo **right_appinfos; - int right_nappinfos; - - memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo)); - left_appinfos = find_appinfos_by_relids(root, left_relids, - &left_nappinfos); - right_appinfos = find_appinfos_by_relids(root, right_relids, - &right_nappinfos); - - sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand, - left_nappinfos, left_appinfos); - sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand, - right_nappinfos, - right_appinfos); - sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand, - left_nappinfos, left_appinfos); - sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand, - right_nappinfos, - right_appinfos); - sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root, - (Node *) sjinfo->semi_rhs_exprs, - right_nappinfos, - right_appinfos); - - pfree(left_appinfos); - pfree(right_appinfos); - - return sjinfo; -} - -/* - * find_appinfos_by_relids - * Find AppendRelInfo structures for all relations specified by relids. - * - * The AppendRelInfos are returned in an array, which can be pfree'd by the - * caller. *nappinfos is set to the number of entries in the array. - */ -AppendRelInfo ** -find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos) -{ - AppendRelInfo **appinfos; - int cnt = 0; - int i; - - *nappinfos = bms_num_members(relids); - appinfos = (AppendRelInfo **) palloc(sizeof(AppendRelInfo *) * *nappinfos); - - i = -1; - while ((i = bms_next_member(relids, i)) >= 0) - { - AppendRelInfo *appinfo = root->append_rel_array[i]; - - if (!appinfo) - elog(ERROR, "child rel %d not found in append_rel_array", i); - - appinfos[cnt++] = appinfo; - } - return appinfos; -} diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile index da9ccf32b4..d166030a10 100644 --- a/src/backend/optimizer/util/Makefile +++ b/src/backend/optimizer/util/Makefile @@ -12,7 +12,8 @@ subdir = src/backend/optimizer/util top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = clauses.o joininfo.o orclauses.o pathnode.o placeholder.o \ - plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o +OBJS = append.o appendinfo.o clauses.o joininfo.o orclauses.o pathnode.o \ + placeholder.o plancat.o predtest.o relnode.o restrictinfo.o tlist.o \ + var.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/optimizer/util/append.c b/src/backend/optimizer/util/append.c new file mode 100644 index 0000000000..d2ad80034f --- /dev/null +++ b/src/backend/optimizer/util/append.c @@ -0,0 +1,777 @@ +/*------------------------------------------------------------------------- + * + * append.c + * Routines to process children of an appendrel parent + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/optimizer/path/append.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <limits.h> + +#include "access/sysattr.h" +#include "catalog/partition.h" +#include "catalog/pg_class.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_type.h" +#include "nodes/makefuncs.h" +#include "optimizer/append.h" +#include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" +#include "optimizer/plancat.h" +#include "optimizer/planmain.h" +#include "optimizer/planner.h" +#include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" +#include "optimizer/tlist.h" +#include "optimizer/var.h" +#include "partitioning/partprune.h" +#include "utils/rel.h" + +static void expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti); +static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, + Index rti, RelOptInfo *rel); +static void expand_partitioned_rtentry(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p); +static RelOptInfo *build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); + + +/* + * expand_inherited_tables + * Expand each rangetable entry that represents an inheritance set + * into an "append relation". At the conclusion of this process, + * the "inh" flag is set in all and only those RTEs that are append + * relation parents. + */ +void +expand_inherited_tables(PlannerInfo *root) +{ + int orig_rtable_size; + Index rti; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; + + /* + * expand_append_rtentry may add RTEs to parse->rtable. The function is + * expected to recursively handle any RTEs that it creates with inh=true. + * So just scan as far as the original end of the rtable list. + */ + for (rti = 1; rti < orig_rtable_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; + + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + if (rte->inh) + expand_append_rtentry(root, brel, rte, rti); + } +} + +/* + * expand_append_rtentry + * This initializes RelOptInfos for an appendrel's child relations, if + * any + * + * 'rel' is the appendrel parent, whose range table entry ('rte') has been + * marked to require adding children. An appendrel parent could either + * be a subquery (if we flattened UNION ALL query) or a table that's known + * to have inheritance children. The latter consists of both regular + * inheritance parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as an appendrel child. The original RTE is + * considered to represent the whole inheritance set. + */ +static void +expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, + Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_append_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_inherited_rtentry(root, rte, rti, rel); + } +} + +/* + * expand_inherited_rtentry + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. + * + * Note that the original RTE is considered to represent the whole + * inheritance set. The first of the generated RTEs is an RTE for the same + * table, but with inh = false, to represent the parent table in its role + * as a simple member of the inheritance set. + * + * A childless table is never considered to be an inheritance set. For + * regular inheritance, a parent RTE must always have at least two associated + * AppendRelInfos: one corresponding to the parent table as a simple member of + * inheritance set and one or more corresponding to the actual children. + */ +static void +expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, + RelOptInfo *rel) +{ + Oid parentOID; + PlanRowMark *oldrc; + LOCKMODE lockmode = rte->rellockmode; + List *inhOIDs; + ListCell *l; + int num_children; + int num_children_added = 0; + + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); + parentOID = rte->relid; + + /* Scan for all members of inheritance set, acquire needed locks */ + inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); + + /* + * Check that there's at least one descendant, else treat as no-child + * case. This could happen despite above has_subclass() check, if table + * once had a child but no longer does. + */ + num_children = list_length(inhOIDs); + if (num_children < 2) + { + /* Clear flag before returning */ + rte->inh = false; + return; + } + + /* + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. + */ + oldrc = get_plan_rowmark(root->rowMarks, rti); + Assert(oldrc == NULL || oldrc->isParent); + + /* + * Must expand PlannerInfo arrays by num_children before we can add + * children. + */ + expand_planner_arrays(root, num_children); + + foreach(l, inhOIDs) + { + Oid childOID = lfirst_oid(l); + Relation newrelation; + RangeTblEntry *childrte; + Index childRTindex; + + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + + /* + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. + */ + if (RELATION_IS_OTHER_TEMP(newrelation)) + { + heap_close(newrelation, lockmode); + continue; + } + + (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation, &childrte, + &childRTindex); + Assert(childrte != NULL); + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + Assert(childRTindex > 0); + + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; + } + + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } +} + +/* + * expand_partitioned_rtentry + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * in order to avoid partitions being locked in a different order than other + * places in the backend that may lock partitions. + */ +static void +expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel) +{ + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; + int i; + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; + + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); + + /* + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. + */ + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); + + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + expand_planner_arrays(root, bms_num_members(partindexes)); + + /* + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. + */ + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); + + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) + { + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + RangeTblEntry *childrte; + Index childRTindex; + + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); + + /* + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. + */ + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } + + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation, + &childrte, &childRTindex); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; + + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + + /* If the child is partitioned itself, expand it too. */ + if (childrel->part_scheme) + { + Assert(childrte->inh); + expand_partitioned_rtentry(root, childrte, childRTindex, + childrel); + } + } + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); + } +} + +/* + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. + * + * The return value is the RelOptInfo that's added. + * + * PlanRowMarks still carry the top-parent's RTI, and the top-parent's + * allMarkTypes field still accumulates values from all descendents. + * + * "parentrte" and "parentRTindex" are immediate parent's RTE and + * RTI. "top_parentrc" is top parent's PlanRowMark. + */ +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p) +{ + Query *parse = root->parse; + Oid childOID = RelationGetRelid(childrel); + RangeTblEntry *childrte; + Index childRTindex; + AppendRelInfo *appinfo; + RelOptInfo *childrelopt; + + /* + * Build an RTE for the child, and attach to query's rangetable list. We + * copy most fields of the parent's RTE, but replace relation OID and + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties + * individual children may have. (This is an intentional choice to make + * inherited RLS work like regular permissions checks.) The parent + * securityQuals will be propagated to children along with other base + * restriction clauses, so we don't need to do it here. + */ + childrte = copyObject(parentrte); + *childrte_p = childrte; + childrte->relid = childOID; + childrte->relkind = childrel->rd_rel->relkind; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); + childrte->requiredPerms = 0; + childrte->securityQuals = NIL; + parse->rtable = lappend(parse->rtable, childrte); + childRTindex = list_length(parse->rtable); + *childRTindex_p = childRTindex; + + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + childrel->rd_att, + childOID, + childrel->rd_rel->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); + + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); + } + + /* + * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE. + */ + if (top_parentrc) + { + PlanRowMark *childrc = makeNode(PlanRowMark); + + childrc->rti = childRTindex; + childrc->prti = top_parentrc->rti; + childrc->rowmarkId = top_parentrc->rowmarkId; + /* Reselect rowmark type, because relkind might not match parent */ + childrc->markType = select_rowmark_type(childrte, + top_parentrc->strength); + childrc->allMarkTypes = (1 << childrc->markType); + childrc->strength = top_parentrc->strength; + childrc->waitPolicy = top_parentrc->waitPolicy; + + /* + * We mark RowMarks for partitioned child tables as parent RowMarks so + * that the executor ignores them (except their existence means that + * the child tables be locked using appropriate mode). + */ + childrc->isParent = (childrte->relkind == RELKIND_PARTITIONED_TABLE); + + /* Include child's rowmark type in top parent's allMarkTypes */ + top_parentrc->allMarkTypes |= childrc->allMarkTypes; + + root->rowMarks = lappend(root->rowMarks, childrc); + } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need need to create + * one, because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_append_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; +} + +/* + * build_append_child_rel + * Build a RelOptInfo for child relation of an append rel + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields base on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turns out to be self-contradictory, the + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + List *childquals; + ListCell *lc; + bool have_const_false_cq; + Index cq_min_security; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = false; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, parent->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual = (Node *) rinfo->clause; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, childqual, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might + * have securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals.) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except + * that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* + * not likely that we'd see constants here, so no + * check + */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, + false, + false, + security_level, + NULL, NULL, + NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* Set child's version of baserestrictinfo. */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; +} diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c new file mode 100644 index 0000000000..9502535e53 --- /dev/null +++ b/src/backend/optimizer/util/appendinfo.c @@ -0,0 +1,851 @@ +/*------------------------------------------------------------------------- + * + * appendinfo.c + * Routines for mapping between append parent(s) and children + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/optimizer/path/appendinfo.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" +#include "parser/parsetree.h" +#include "utils/rel.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" + +typedef struct +{ + PlannerInfo *root; + int nappinfos; + AppendRelInfo **appinfos; +} adjust_appendrel_attrs_context; + +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); +static Node *adjust_appendrel_attrs_mutator(Node *node, + adjust_appendrel_attrs_context *context); +static Relids adjust_child_relids(Relids relids, int nappinfos, + AppendRelInfo **appinfos); +static List *adjust_inherited_tlist(List *tlist, + AppendRelInfo *context); + +AppendRelInfo * +make_append_rel_info(RelOptInfo *parentrel, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex) +{ + AppendRelInfo *appinfo = makeNode(AppendRelInfo); + + appinfo->parent_relid = parentrel->relid; + appinfo->child_relid = childRTindex; + appinfo->parent_reltype = parentrel->reltype; + appinfo->child_reltype = childreltype; + make_inh_translation_list(parentrel->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, + &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; + + return appinfo; +} + +/* + * adjust_appendrel_attrs + * Copy the specified query or expression and translate Vars referring to a + * parent rel to refer to the corresponding child rel instead. We also + * update rtindexes appearing outside Vars, such as resultRelation and + * jointree relids. + * + * Note: this is only applied after conversion of sublinks to subplans, + * so we don't need to cope with recursion into sub-queries. + * + * Note: this is not hugely different from what pullup_replace_vars() does; + * maybe we should try to fold the two routines together. + */ +Node * +adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, + AppendRelInfo **appinfos) +{ + Node *result; + adjust_appendrel_attrs_context context; + + context.root = root; + context.nappinfos = nappinfos; + context.appinfos = appinfos; + + /* If there's nothing to adjust, don't call this function. */ + Assert(nappinfos >= 1 && appinfos != NULL); + + /* + * Must be prepared to start with a Query or a bare expression tree. + */ + if (node && IsA(node, Query)) + { + Query *newnode; + int cnt; + + newnode = query_tree_mutator((Query *) node, + adjust_appendrel_attrs_mutator, + (void *) &context, + QTW_IGNORE_RC_SUBQUERIES); + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (newnode->resultRelation == appinfo->parent_relid) + { + newnode->resultRelation = appinfo->child_relid; + /* Fix tlist resnos too, if it's inherited UPDATE */ + if (newnode->commandType == CMD_UPDATE) + newnode->targetList = + adjust_inherited_tlist(newnode->targetList, + appinfo); + break; + } + } + + result = (Node *) newnode; + } + else + result = adjust_appendrel_attrs_mutator(node, &context); + + return result; +} + +static Node * +adjust_appendrel_attrs_mutator(Node *node, + adjust_appendrel_attrs_context *context) +{ + AppendRelInfo **appinfos = context->appinfos; + int nappinfos = context->nappinfos; + int cnt; + + if (node == NULL) + return NULL; + if (IsA(node, Var)) + { + Var *var = (Var *) copyObject(node); + AppendRelInfo *appinfo = NULL; + + for (cnt = 0; cnt < nappinfos; cnt++) + { + if (var->varno == appinfos[cnt]->parent_relid) + { + appinfo = appinfos[cnt]; + break; + } + } + + if (var->varlevelsup == 0 && appinfo) + { + var->varno = appinfo->child_relid; + var->varnoold = appinfo->child_relid; + if (var->varattno > 0) + { + Node *newnode; + + if (var->varattno > list_length(appinfo->translated_vars)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + var->varattno, get_rel_name(appinfo->parent_reloid)); + newnode = copyObject(list_nth(appinfo->translated_vars, + var->varattno - 1)); + if (newnode == NULL) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + var->varattno, get_rel_name(appinfo->parent_reloid)); + return newnode; + } + else if (var->varattno == 0) + { + /* + * Whole-row Var: if we are dealing with named rowtypes, we + * can use a whole-row Var for the child table plus a coercion + * step to convert the tuple layout to the parent's rowtype. + * Otherwise we have to generate a RowExpr. + */ + if (OidIsValid(appinfo->child_reltype)) + { + Assert(var->vartype == appinfo->parent_reltype); + if (appinfo->parent_reltype != appinfo->child_reltype) + { + ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr); + + r->arg = (Expr *) var; + r->resulttype = appinfo->parent_reltype; + r->convertformat = COERCE_IMPLICIT_CAST; + r->location = -1; + /* Make sure the Var node has the right type ID, too */ + var->vartype = appinfo->child_reltype; + return (Node *) r; + } + } + else + { + /* + * Build a RowExpr containing the translated variables. + * + * In practice var->vartype will always be RECORDOID here, + * so we need to come up with some suitable column names. + * We use the parent RTE's column names. + * + * Note: we can't get here for inheritance cases, so there + * is no need to worry that translated_vars might contain + * some dummy NULLs. + */ + RowExpr *rowexpr; + List *fields; + RangeTblEntry *rte; + + rte = rt_fetch(appinfo->parent_relid, + context->root->parse->rtable); + fields = copyObject(appinfo->translated_vars); + rowexpr = makeNode(RowExpr); + rowexpr->args = fields; + rowexpr->row_typeid = var->vartype; + rowexpr->row_format = COERCE_IMPLICIT_CAST; + rowexpr->colnames = copyObject(rte->eref->colnames); + rowexpr->location = -1; + + return (Node *) rowexpr; + } + } + /* system attributes don't need any other translation */ + } + return (Node *) var; + } + if (IsA(node, CurrentOfExpr)) + { + CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node); + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (cexpr->cvarno == appinfo->parent_relid) + { + cexpr->cvarno = appinfo->child_relid; + break; + } + } + return (Node *) cexpr; + } + if (IsA(node, RangeTblRef)) + { + RangeTblRef *rtr = (RangeTblRef *) copyObject(node); + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (rtr->rtindex == appinfo->parent_relid) + { + rtr->rtindex = appinfo->child_relid; + break; + } + } + return (Node *) rtr; + } + if (IsA(node, JoinExpr)) + { + /* Copy the JoinExpr node with correct mutation of subnodes */ + JoinExpr *j; + AppendRelInfo *appinfo; + + j = (JoinExpr *) expression_tree_mutator(node, + adjust_appendrel_attrs_mutator, + (void *) context); + /* now fix JoinExpr's rtindex (probably never happens) */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + appinfo = appinfos[cnt]; + + if (j->rtindex == appinfo->parent_relid) + { + j->rtindex = appinfo->child_relid; + break; + } + } + return (Node *) j; + } + if (IsA(node, PlaceHolderVar)) + { + /* Copy the PlaceHolderVar node with correct mutation of subnodes */ + PlaceHolderVar *phv; + + phv = (PlaceHolderVar *) expression_tree_mutator(node, + adjust_appendrel_attrs_mutator, + (void *) context); + /* now fix PlaceHolderVar's relid sets */ + if (phv->phlevelsup == 0) + phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos, + context->appinfos); + return (Node *) phv; + } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + + /* Shouldn't need to handle planner auxiliary nodes here */ + Assert(!IsA(node, AppendRelInfo)); + Assert(!IsA(node, PlaceHolderInfo)); + Assert(!IsA(node, MinMaxAggInfo)); + + /* + * We have to process RestrictInfo nodes specially. (Note: although + * set_append_rel_pathlist will hide RestrictInfos in the parent's + * baserestrictinfo list from us, it doesn't hide those in joininfo.) + */ + if (IsA(node, RestrictInfo)) + { + RestrictInfo *oldinfo = (RestrictInfo *) node; + RestrictInfo *newinfo = makeNode(RestrictInfo); + + /* Copy all flat-copiable fields */ + memcpy(newinfo, oldinfo, sizeof(RestrictInfo)); + + /* Recursively fix the clause itself */ + newinfo->clause = (Expr *) + adjust_appendrel_attrs_mutator((Node *) oldinfo->clause, context); + + /* and the modified version, if an OR clause */ + newinfo->orclause = (Expr *) + adjust_appendrel_attrs_mutator((Node *) oldinfo->orclause, context); + + /* adjust relid sets too */ + newinfo->clause_relids = adjust_child_relids(oldinfo->clause_relids, + context->nappinfos, + context->appinfos); + newinfo->required_relids = adjust_child_relids(oldinfo->required_relids, + context->nappinfos, + context->appinfos); + newinfo->outer_relids = adjust_child_relids(oldinfo->outer_relids, + context->nappinfos, + context->appinfos); + newinfo->nullable_relids = adjust_child_relids(oldinfo->nullable_relids, + context->nappinfos, + context->appinfos); + newinfo->left_relids = adjust_child_relids(oldinfo->left_relids, + context->nappinfos, + context->appinfos); + newinfo->right_relids = adjust_child_relids(oldinfo->right_relids, + context->nappinfos, + context->appinfos); + + /* + * Reset cached derivative fields, since these might need to have + * different values when considering the child relation. Note we + * don't reset left_ec/right_ec: each child variable is implicitly + * equivalent to its parent, so still a member of the same EC if any. + */ + newinfo->eval_cost.startup = -1; + newinfo->norm_selec = -1; + newinfo->outer_selec = -1; + newinfo->left_em = NULL; + newinfo->right_em = NULL; + newinfo->scansel_cache = NIL; + newinfo->left_bucketsize = -1; + newinfo->right_bucketsize = -1; + newinfo->left_mcvfreq = -1; + newinfo->right_mcvfreq = -1; + + return (Node *) newinfo; + } + + /* + * NOTE: we do not need to recurse into sublinks, because they should + * already have been converted to subplans before we see them. + */ + Assert(!IsA(node, SubLink)); + Assert(!IsA(node, Query)); + + return expression_tree_mutator(node, adjust_appendrel_attrs_mutator, + (void *) context); +} + +/* + * Substitute child relids for parent relids in a Relid set. The array of + * appinfos specifies the substitutions to be performed. + */ +static Relids +adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos) +{ + Bitmapset *result = NULL; + int cnt; + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + /* Remove parent, add child */ + if (bms_is_member(appinfo->parent_relid, relids)) + { + /* Make a copy if we are changing the set. */ + if (!result) + result = bms_copy(relids); + + result = bms_del_member(result, appinfo->parent_relid); + result = bms_add_member(result, appinfo->child_relid); + } + } + + /* If we made any changes, return the modified copy. */ + if (result) + return result; + + /* Otherwise, return the original set without modification. */ + return relids; +} + +/* + * Replace any relid present in top_parent_relids with its child in + * child_relids. Members of child_relids can be multiple levels below top + * parent in the partition hierarchy. + */ +Relids +adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, + Relids child_relids, Relids top_parent_relids) +{ + AppendRelInfo **appinfos; + int nappinfos; + Relids parent_relids = NULL; + Relids result; + Relids tmp_result = NULL; + int cnt; + + /* + * If the given relids set doesn't contain any of the top parent relids, + * it will remain unchanged. + */ + if (!bms_overlap(relids, top_parent_relids)) + return relids; + + appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); + + /* Construct relids set for the immediate parent of the given child. */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); + } + + /* Recurse if immediate parent is not the top parent. */ + if (!bms_equal(parent_relids, top_parent_relids)) + { + tmp_result = adjust_child_relids_multilevel(root, relids, + parent_relids, + top_parent_relids); + relids = tmp_result; + } + + result = adjust_child_relids(relids, nappinfos, appinfos); + + /* Free memory consumed by any intermediate result. */ + if (tmp_result) + bms_free(tmp_result); + bms_free(parent_relids); + pfree(appinfos); + + return result; +} + +/* + * adjust_appendrel_attrs_multilevel + * Apply Var translations from a toplevel appendrel parent down to a child. + * + * In some cases we need to translate expressions referencing a parent relation + * to reference an appendrel child that's multiple levels removed from it. + */ +Node * +adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, + Relids child_relids, + Relids top_parent_relids) +{ + AppendRelInfo **appinfos; + Bitmapset *parent_relids = NULL; + int nappinfos; + int cnt; + + Assert(bms_num_members(child_relids) == bms_num_members(top_parent_relids)); + + appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); + + /* Construct relids set for the immediate parent of given child. */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); + } + + /* Recurse if immediate parent is not the top parent. */ + if (!bms_equal(parent_relids, top_parent_relids)) + node = adjust_appendrel_attrs_multilevel(root, node, parent_relids, + top_parent_relids); + + /* Now translate for this child */ + node = adjust_appendrel_attrs(root, node, nappinfos, appinfos); + + pfree(appinfos); + + return node; +} + +/* + * Construct the SpecialJoinInfo for a child-join by translating + * SpecialJoinInfo for the join between parents. left_relids and right_relids + * are the relids of left and right side of the join respectively. + */ +SpecialJoinInfo * +build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, + Relids left_relids, Relids right_relids) +{ + SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo); + AppendRelInfo **left_appinfos; + int left_nappinfos; + AppendRelInfo **right_appinfos; + int right_nappinfos; + + memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo)); + left_appinfos = find_appinfos_by_relids(root, left_relids, + &left_nappinfos); + right_appinfos = find_appinfos_by_relids(root, right_relids, + &right_nappinfos); + + sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand, + left_nappinfos, left_appinfos); + sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand, + right_nappinfos, + right_appinfos); + sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand, + left_nappinfos, left_appinfos); + sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand, + right_nappinfos, + right_appinfos); + sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root, + (Node *) sjinfo->semi_rhs_exprs, + right_nappinfos, + right_appinfos); + + pfree(left_appinfos); + pfree(right_appinfos); + + return sjinfo; +} + +/* + * find_appinfos_by_relids + * Find AppendRelInfo structures for all relations specified by relids. + * + * The AppendRelInfos are returned in an array, which can be pfree'd by the + * caller. *nappinfos is set to the number of entries in the array. + */ +AppendRelInfo ** +find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos) +{ + AppendRelInfo **appinfos; + int cnt = 0; + int i; + + *nappinfos = bms_num_members(relids); + appinfos = (AppendRelInfo **) palloc(sizeof(AppendRelInfo *) * *nappinfos); + + i = -1; + while ((i = bms_next_member(relids, i)) >= 0) + { + AppendRelInfo *appinfo = root->append_rel_array[i]; + + if (!appinfo) + elog(ERROR, "child rel %d not found in append_rel_array", i); + + appinfos[cnt++] = appinfo; + } + return appinfos; +} + +/* + * make_inh_translation_list + * Build the list of translations from parent Vars to child Vars for + * an inheritance child. + * + * For paranoia's sake, we match type/collation as well as attribute name. + */ +static void +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) +{ + List *vars = NIL; + int oldnatts = old_tupdesc->natts; + int newnatts = new_tupdesc->natts; + int old_attno; + int new_attno = 0; + + for (old_attno = 0; old_attno < oldnatts; old_attno++) + { + Form_pg_attribute att; + char *attname; + Oid atttypid; + int32 atttypmod; + Oid attcollation; + + att = TupleDescAttr(old_tupdesc, old_attno); + if (att->attisdropped) + { + /* Just put NULL into this list entry */ + vars = lappend(vars, NULL); + continue; + } + attname = NameStr(att->attname); + atttypid = att->atttypid; + atttypmod = att->atttypmod; + attcollation = att->attcollation; + + /* + * When we are generating the "translation list" for the parent table + * of an inheritance set, no need to search for matches. + */ + if (from_rel == to_rel) + { + vars = lappend(vars, makeVar(newvarno, + (AttrNumber) (old_attno + 1), + atttypid, + atttypmod, + attcollation, + 0)); + continue; + } + + /* + * Otherwise we have to search for the matching column by name. + * There's no guarantee it'll have the same column position, because + * of cases like ALTER TABLE ADD COLUMN and multiple inheritance. + * However, in simple cases, the relative order of columns is mostly + * the same in both relations, so try the column of newrelation that + * follows immediately after the one that we just found, and if that + * fails, let syscache handle it. + */ + if (new_attno >= newnatts || + (att = TupleDescAttr(new_tupdesc, new_attno))->attisdropped || + strcmp(attname, NameStr(att->attname)) != 0) + { + HeapTuple newtup; + + newtup = SearchSysCacheAttName(to_rel, attname); + if (!newtup) + elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", + attname, get_rel_name(to_rel)); + new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; + ReleaseSysCache(newtup); + + att = TupleDescAttr(new_tupdesc, new_attno); + } + + /* Found it, check type and collation match */ + if (atttypid != att->atttypid || atttypmod != att->atttypmod) + elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", + attname, get_rel_name(to_rel)); + if (attcollation != att->attcollation) + elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", + attname, get_rel_name(to_rel)); + + vars = lappend(vars, makeVar(newvarno, + (AttrNumber) (new_attno + 1), + atttypid, + atttypmod, + attcollation, + 0)); + new_attno++; + } + + *translated_vars = vars; +} + +/* + * translate_col_privs + * Translate a bitmapset representing per-column privileges from the + * parent rel's attribute numbering to the child's. + * + * The only surprise here is that we don't translate a parent whole-row + * reference into a child whole-row reference. That would mean requiring + * permissions on all child columns, which is overly strict, since the + * query is really only going to reference the inherited columns. Instead + * we set the per-column bits for all inherited columns. + */ +Bitmapset * +translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars) +{ + Bitmapset *child_privs = NULL; + bool whole_row; + int attno; + ListCell *lc; + + /* System attributes have the same numbers in all tables */ + for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) + { + if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + attno - FirstLowInvalidHeapAttributeNumber); + } + + /* Check if parent has whole-row reference */ + whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, + parent_privs); + + /* And now translate the regular user attributes, using the vars list */ + attno = InvalidAttrNumber; + foreach(lc, translated_vars) + { + Var *var = lfirst_node(Var, lc); + + attno++; + if (var == NULL) /* ignore dropped columns */ + continue; + if (whole_row || + bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + var->varattno - FirstLowInvalidHeapAttributeNumber); + } + + return child_privs; +} + +/* + * Adjust the targetlist entries of an inherited UPDATE operation + * + * The expressions have already been fixed, but we have to make sure that + * the target resnos match the child table (they may not, in the case of + * a column that was added after-the-fact by ALTER TABLE). In some cases + * this can force us to re-order the tlist to preserve resno ordering. + * (We do all this work in special cases so that preptlist.c is fast for + * the typical case.) + * + * The given tlist has already been through expression_tree_mutator; + * therefore the TargetEntry nodes are fresh copies that it's okay to + * scribble on. + * + * Note that this is not needed for INSERT because INSERT isn't inheritable. + */ +static List * +adjust_inherited_tlist(List *tlist, AppendRelInfo *context) +{ + bool changed_it = false; + ListCell *tl; + List *new_tlist; + bool more; + int attrno; + + /* This should only happen for an inheritance case, not UNION ALL */ + Assert(OidIsValid(context->parent_reloid)); + + /* Scan tlist and update resnos to match attnums of child rel */ + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + Var *childvar; + + if (tle->resjunk) + continue; /* ignore junk items */ + + /* Look up the translation of this column: it must be a Var */ + if (tle->resno <= 0 || + tle->resno > list_length(context->translated_vars)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + tle->resno, get_rel_name(context->parent_reloid)); + childvar = (Var *) list_nth(context->translated_vars, tle->resno - 1); + if (childvar == NULL || !IsA(childvar, Var)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + tle->resno, get_rel_name(context->parent_reloid)); + + if (tle->resno != childvar->varattno) + { + tle->resno = childvar->varattno; + changed_it = true; + } + } + + /* + * If we changed anything, re-sort the tlist by resno, and make sure + * resjunk entries have resnos above the last real resno. The sort + * algorithm is a bit stupid, but for such a seldom-taken path, small is + * probably better than fast. + */ + if (!changed_it) + return tlist; + + new_tlist = NIL; + more = true; + for (attrno = 1; more; attrno++) + { + more = false; + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + + if (tle->resjunk) + continue; /* ignore junk items */ + + if (tle->resno == attrno) + new_tlist = lappend(new_tlist, tle); + else if (tle->resno > attrno) + more = true; + } + } + + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + + if (!tle->resjunk) + continue; /* here, ignore non-junk items */ + + tle->resno = attrno; + new_tlist = lappend(new_tlist, tle); + attrno++; + } + + return new_tlist; +} diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index d50d86b252..8ce88876c4 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -20,6 +20,7 @@ #include "foreign/fdwapi.h" #include "nodes/extensible.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index c23db9d78d..52a11f434f 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -17,6 +17,7 @@ #include <limits.h> #include "miscadmin.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index fcf8d6032c..e76906da1f 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -44,6 +44,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/include/optimizer/append.h b/src/include/optimizer/append.h new file mode 100644 index 0000000000..b5f025c137 --- /dev/null +++ b/src/include/optimizer/append.h @@ -0,0 +1,25 @@ +/*------------------------------------------------------------------------- + * + * append.h + * prototypes for append.c. + * + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/optimizer/append.h + * + *------------------------------------------------------------------------- + */ +#ifndef APPEND_H +#define APPEND_H + +#include "nodes/relation.h" + +/* + * append.c + * utilities for dealing with append relations + */ +extern void expand_inherited_tables(PlannerInfo *root); + +#endif /* APPEND_H */ diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h new file mode 100644 index 0000000000..e205e78e6d --- /dev/null +++ b/src/include/optimizer/appendinfo.h @@ -0,0 +1,43 @@ +/*------------------------------------------------------------------------- + * + * appendinfo.h + * Routines for mapping expressions between append rel parent(s) and + * children + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/optimizer/appendinfo.h + * + *------------------------------------------------------------------------- + */ +#ifndef APPENDINFO_H +#define APPENDINFO_H + +#include "nodes/plannodes.h" +#include "nodes/relation.h" +#include "utils/relcache.h" + +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parentrel, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex); +extern Bitmapset *translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars); +extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, + int nappinfos, AppendRelInfo **appinfos); + +extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, + Relids child_relids, + Relids top_parent_relids); + +extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root, + Relids relids, int *nappinfos); + +extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, + SpecialJoinInfo *parent_sjinfo, + Relids left_relids, Relids right_relids); +extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, + Relids child_relids, Relids top_parent_relids); + +#endif /* APPENDINFO_H */ diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index faae07d240..3ad9b4a77e 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -48,24 +48,5 @@ extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); */ extern RelOptInfo *plan_set_operations(PlannerInfo *root); -extern void expand_inherited_tables(PlannerInfo *root); -extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, - int nappinfos, AppendRelInfo **appinfos); -extern AppendRelInfo *make_append_rel_info(RelOptInfo *parentrel, - RangeTblEntry *parentrte, - TupleDesc childdesc, Oid childoid, Oid childreltype, - Index childRTindex); -extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, - Relids child_relids, - Relids top_parent_relids); - -extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root, - Relids relids, int *nappinfos); - -extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, - SpecialJoinInfo *parent_sjinfo, - Relids left_relids, Relids right_relids); -extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, - Relids child_relids, Relids top_parent_relids); #endif /* PREP_H */ -- 2.11.0
v7-0005-Teach-planner-to-only-process-unpruned-partitions.patch (text/plain, 7.2K) download From a26d82f90e2182ed2f0f3f276c9f2e240c512c00 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 15 Oct 2018 10:59:24 +0900 Subject: [PATCH v7 5/6] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/append.c | 8 ++++++++ src/backend/optimizer/util/relnode.c | 3 +++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/relation.h | 2 ++ 6 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 6e321ec9e7..76531a05cf 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1424,6 +1424,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 3513b25cac..c0487d7ae2 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6762,7 +6762,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -6770,9 +6772,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -6853,7 +6853,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -6865,7 +6864,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -6875,9 +6876,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/append.c b/src/backend/optimizer/util/append.c index d2ad80034f..3b3fb304b9 100644 --- a/src/backend/optimizer/util/append.c +++ b/src/backend/optimizer/util/append.c @@ -351,6 +351,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, /* Perform pruning. */ partindexes = prune_append_rel_partitions(parentrel); + parentrel->live_parts = partindexes; /* Must expand PlannerInfo arrays before we can add children. */ expand_planner_arrays(root, bms_num_members(partindexes)); @@ -394,6 +395,13 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, Assert(childrel != NULL); parentrel->part_rels[i] = childrel; + /* + * If partition is excluded by constraints, remove it from + * live_parts, too. + */ + if (IS_DUMMY_REL(childrel)) + parentrel->live_parts = bms_del_member(parentrel->live_parts, i); + /* Close child relations, but keep locks */ heap_close(newrelation, NoLock); diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 52a11f434f..6c7eafe6db 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1748,6 +1748,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index e76906da1f..8bc2f5c5c2 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -441,29 +441,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index e6fdbcd030..f43fa46fc5 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -727,6 +727,8 @@ typedef struct RelOptInfo Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Set of live partitions; contains indexes + * into part_rels array */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.11.0
v7-0006-Do-not-lock-all-partitions-at-the-beginning.patch (text/plain, 1.2K) download From 6954a852b359c439414937d60fc00b7a0f91c886 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 12 Oct 2018 19:12:53 +0900 Subject: [PATCH v7 6/6] Do not lock all partitions at the beginning --- src/backend/optimizer/util/append.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/backend/optimizer/util/append.c b/src/backend/optimizer/util/append.c index 3b3fb304b9..0b0b75d409 100644 --- a/src/backend/optimizer/util/append.c +++ b/src/backend/optimizer/util/append.c @@ -335,10 +335,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, parentrel->inh_root_parent : parentRTindex; - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - /* * Initialize partitioned_child_rels to contain this RT index. * @@ -374,8 +370,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, RangeTblEntry *childrte; Index childRTindex; - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); + newrelation = heap_open(childOID, lockmode); Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2018-11-15 01:19  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 1 reply; 265+ messages in thread

From: Imai, Yoshikazu @ 2018-11-15 01:19 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Rowley <david.rowley@2ndquadrant.com>; pgsql-hackers

Hi Amit,

On Tue, Nov 13, 2018 at 10:29 PM, Amit Langote wrote:
> On 2018/11/12 13:35, Imai, Yoshikazu wrote:
> > adjust_appendrel_attrs_multilevel for leaf1: root -> sub1 -> leaf1
> > adjust_appendrel_attrs_multilevel for leaf2: root -> sub1 -> leaf2
> 
> Ah, I see what you mean.
> 
> The root -> sub1 translation will be repeated for each leaf partition
> if done via adjust_appendrel_attrs_multilevel.  On the other hand, if
> we could do the root to sub1 translation once and pass it to the recursive
> call using sub1 as the parent.
> 
> I've changed the patch use adjust_appendrel_attrs.
> 
> > Since it is difficult to explain my thoughts with words, I will show
> > the performance degration case.
> >
> > Partition tables are below two sets.
> 
> [ ... ]
> 
> > Create a generic plan of updation or deletion.
> >
> > [create a delete generic plan]
> > set plan_cache_mode = 'force_generic_plan'; prepare delete_stmt(int)
> > as delete from rt where b = $1; execute delete_stmt(1);
> 
> [ ... ]
> 
> > How amount of memory is used with above tests is...
> >
> > without v5 patches, Set1: 242MB
> > without v5 patches, Set2: 247MB
> > with v5 patches, Set1: 420MB
> > with v5 patches, Set2: 820MB
> 
> Although I didn't aim to fix planning for the generic plan case where
> no pruning occurs, the above result is not acceptable.  That is, the new
> implementation of inheritance update/delete planning shouldn't consume
> more memory than the previous.  In fact, it should've consumed less,
> because the patch claims that it gets rid of redundant processing per
> partition.
> 
> I understood why update/delete planning consumed more memory with the
> patch.  It was due to a problem with the patch that modifies inheritance
> update/delete planning.  The exact problem was that the query tree would
> be translated (hence copied) *twice* for every partition!  First during
> query planning where the query tree would be translated to figure out
> a targetlist for partitions and then again before calling
> grouping_planner.
> Also, the adjust_appendrel_attrs_multilevel made it worse for
> multi-level partitioning case, because of repeated copying for root to
> intermediate partitioned tables, as Imai-san pointed out.
> 
> I've fixed that making sure that query tree is translated only once and
> saved for later steps to use.  Imai-san, please check the memory
> consumption with the latest patch.

Thanks for fixing!
Now, memory consumption is lower than the previous.

with v7 patches, Set1: 223MB
with v7 patches, Set2: 226MB

Thanks,

--
Yoshikazu Imai


^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-11-15 01:24  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  0 siblings, 0 replies; 265+ messages in thread

From: Amit Langote @ 2018-11-15 01:24 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Rowley <david.rowley@2ndquadrant.com>; pgsql-hackers

On 2018/11/15 10:19, Imai, Yoshikazu wrote:
> On Tue, Nov 13, 2018 at 10:29 PM, Amit Langote wrote:
>> On 2018/11/12 13:35, Imai, Yoshikazu wrote:
>>> How amount of memory is used with above tests is...
>>>
>>> without v5 patches, Set1: 242MB
>>> without v5 patches, Set2: 247MB
>>> with v5 patches, Set1: 420MB
>>> with v5 patches, Set2: 820MB
>>
>> I understood why update/delete planning consumed more memory with the
>> patch.  It was due to a problem with the patch that modifies inheritance
>> update/delete planning.  The exact problem was that the query tree would
>> be translated (hence copied) *twice* for every partition!  First during
>> query planning where the query tree would be translated to figure out
>> a targetlist for partitions and then again before calling
>> grouping_planner.
>> Also, the adjust_appendrel_attrs_multilevel made it worse for
>> multi-level partitioning case, because of repeated copying for root to
>> intermediate partitioned tables, as Imai-san pointed out.
>>
>> I've fixed that making sure that query tree is translated only once and
>> saved for later steps to use.  Imai-san, please check the memory
>> consumption with the latest patch.
> 
> Thanks for fixing!
> Now, memory consumption is lower than the previous.
> 
> with v7 patches, Set1: 223MB
> with v7 patches, Set2: 226MB

Thanks for checking.  So at least we no longer have any memory
over-allocation bug with the patch, but perhaps other bugs are still
lurking. :)

Regards,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-11-21 10:23  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 1 reply; 265+ messages in thread

From: Amit Langote @ 2018-11-21 10:23 UTC (permalink / raw)
  To: David Rowley <david.rowley@2ndquadrant.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers

On 2018/11/14 19:28, Amit Langote wrote:
> On 2018/11/10 20:59, David Rowley wrote:
>> 8. In regards to:
>>
>> + * NB: Do we need to change the child EC members to be marked
>> + * as non-child somehow?
>> + */
>> + childrel->reloptkind = RELOPT_BASEREL;
>>
>> I know we talked a bit about this before, but this time I put together
>> a crude patch that runs some code each time we skip an em_is_child ==
>> true EquivalenceMember. The code checks if any of the em_relids are
>> RELOPT_BASEREL. What I'm looking for here are places where we
>> erroneously skip the member when we shouldn't.  Running the regression
>> tests with this patch in place shows a number of problems.  Likely I
>> should only trigger the warning when bms_membership(em->em_relids) ==
>> BMS_SINGLETON, but it never-the-less appears to highlight various
>> possible issues. Applying the same on master only appears to show the
>> cases where em->em_relids isn't a singleton set.  I've attached the
>> patch to let you see what I mean.
> 
> Thanks for this.  I've been thinking about what to do about it, but
> haven't decided what's that yet.  Please let me spend some more time
> thinking on it.  AFAICT, dealing with this will ensure that join planning
> against target child relations can use EC-based optimizations, but it's
> not incorrect as is per se.

I've been considered this a bit more and have some observations to share.
I found that the new inheritance_planner causes regression when the query
involves equivalence classes referencing the target relation, such as in
the following example:

create table ht (a int, b int) partition by hash (a);
create table ht1 partition of ht for values with (modulus 1024, remainder 0);
...
create table ht1024 partition of ht for values with (modulus 1024,
remainder 1023);
create table foo (a int, b int);
update ht set a = foo.a from foo where ht.b = foo.b;

For the above query, an EC containing ht.b and foo.b would be built.  With
the new approach this EC will need to be expanded to add em_is_child EC
members for all un-pruned child tables, whereas with the previous approach
there would be no child members because the EC would be built for the
child as the query's target relation to begin with.  So, with the old
approach there will be {ht1.b, foo.b} for query with ht1 as target
relation, {ht2.b, foo.b} for query with ht2 as target relation and so on.
Whereas with the new approach there will be just one query_planner run and
resulting EC will be {foo.b, ht.b, ht1.b, ht2.b, ...}.  So, the planning
steps that manipulate ECs now have to iterate through many members and
become a bottleneck if there are many un-pruned children.  To my surprise,
those bottlenecks are worse than having to rerun query_planner for each
child table.

So with master, I get the following planning time for the above update
query which btw doesn't prune (3 repeated runs)

Planning Time: 688.830 ms
Planning Time: 690.950 ms
Planning Time: 704.702 ms

And with the previous v7 patch:

Planning Time: 1373.398 ms
Planning Time: 1360.685 ms
Planning Time: 1356.313 ms

I've fixed that in the attached by utilizing the fact that now we build
the child PlannerInfo before we add child EC members.  By modifying
add_child_rel_equivalences such that it *replaces* the parent EC member
with the corresponding child member instead of appending it to the list,
if the child is the target relation.  That happens inside the child
target's private PlannerInfo, so it's harmless.  Also, it is no longer
marked as em_is_child=true, so as a whole, this more or less restores the
original behavior wrt to ECs (also proved by the fact that I now get the
same regression.diffs by applying David's verify_em_child.diff patch [1]
to the patched tree as with applying it to master modulo the varno
differences due to the patched).

With the attached updated patch (again this is with 0 partitions pruned):

Planning Time: 332.503 ms
Planning Time: 334.003 ms
Planning Time: 334.212 ms

If I add an additional condition so that only 1 partition is joined with
foo and the rest pruned, such as the following query (the case we're
trying to optimize):

update ht set a = foo.a from foo where foo.a = ht.b and foo.a = 1

I get the following numbers with master (no change from 0 pruned case):

Planning Time: 727.473 ms
Planning Time: 726.145 ms
Planning Time: 734.458 ms

But with the patches:

Planning Time: 0.797 ms
Planning Time: 0.751 ms
Planning Time: 0.801 ms

Attached v8 patches.

Thanks,
Amit

[1]
https://www.postgresql.org/message-id/CAKJS1f8g9_BzE678BLBm-eoMMEYUUXhDABSpqtAHRUUTrm_vFA%40mail.gma...

Attachments: download all (6 files, .tar.gz)

v8-0001-Overhaul-inheritance-update-delete-planning.patch (text/plain, 56.6K) download From 1df6fede4ec23eea3f149fa399b0feda61e69ae0 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 11:18:12 +0900 Subject: [PATCH v8 1/6] Overhaul inheritance update/delete planning With the current method, inheritance_planner, which handles the planning for update/delete commands targeting inheritance trees, performs the *whole* planning for each child table. That involves translating the query tree to set a given child table in place of the original inheritance root table as the query's target table, followed by calling grouping_planner on the translated query, which performs query planning and additional steps needed to apply correct target list based on the child target table. That's inefficient for two reasons: 1. partprune.c cannot be used to perform partition pruning, because it can only be invoked from query_planner. With the current approach, query_planner only sees the individual partitions in the query tree it receives, not the parent partitioned table. This leaves each partition to be pruned using constraint exclusion. (constraint exclusion cannon prune hash partitions which means UPDATE/DELETE don't support hash partition pruning.) 2. Repeated invocation of query_planner results in allocating large amounts of memory, especially if there are many child tables. Also, it involves repeatedly performing the same processing, such as jointree processing. This commit addresses both of the issues by rewriting portions of inheritance_planner and adding some supporting functionality to allpath.c. With the new implementation, inheritance_planner calls query_planner only once at the beginning with the original unmodified query, which creates the access paths of individual relations after pruning any unnecessary partitions. For each target child relation, it also creates a sub-PartitionInfo containing translated version of the query and a targetlist suitable for the child. Child PlannerInfos are saved in the top PlannerInfo for using later. If the query involves join against the target relation, join paths are created for each target child relation by replacing the original target table in the join tree by a given child table. Join relations (RelOptInfos thereof) for all target child relations are collected in a global list in the top PlannerInfo. After creating the join paths for all target child relations, inheritance_planner calls grouping_planner() on each child join relation using the previously created child PlannerInfo to finish up the paths such that they produce query's top-level target list expanded according to a given child relation's descriptor. grouping_planner()'s interface is modified so that we can pass it what's called a 'planned_rel', a RelOptInfo that already contains the necessary paths needed to produce its output. This removes some existing code in inheritance_planner that dealt with any subquery RTEs in the query. The rationale of that code was that the subquery RTEs may change during each iteration of planning (that is, for different children), so different iterations better use different copies of those RTEs. That was handled by making fresh copies of those RTEs for each iteration of planning which were appended to the range table, accompanied by modifying all expressions and auxiliary structures that referenced the original subquery RTEs to instead reference the copies (that is, change the varnos). This copying meant we would end up adding S * N new entries to the original range table by the time we got to the last unpruned child, where S is the number of subquery RTEs in the original query and N the number of unpruned children. Since with the new code we perform planning just once, I think we don't need this special handling. Actually, there is a regression test output change due to no longer having copies of subquery RTEs (see the diff of partition_join.out file.) Also we no longer need the hack in relation_excluded_by_constraints() involving setting PlannerInfo.inhTargetKind to enable constraint exclusion for target child relation, which is no longer is no longer needed. Constraint exclusion runs during query_planner step described above. Regression test output change in partition_join.out is due to the fact that we no longer create duplicates of subquery RTEs in child range tables. --- doc/src/sgml/ddl.sgml | 14 +- src/backend/nodes/outfuncs.c | 1 - src/backend/optimizer/path/allpaths.c | 328 +++++++++++++++++++- src/backend/optimizer/path/equivclass.c | 27 +- src/backend/optimizer/plan/createplan.c | 10 - src/backend/optimizer/plan/planner.c | 432 +++++++-------------------- src/backend/optimizer/prep/prepjointree.c | 1 - src/backend/optimizer/prep/prepunion.c | 33 +- src/backend/optimizer/util/plancat.c | 50 +--- src/include/nodes/relation.h | 31 +- src/include/optimizer/paths.h | 3 +- src/test/regress/expected/partition_join.out | 4 +- 12 files changed, 517 insertions(+), 417 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 61c4a25460..80f587012c 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -3877,15 +3877,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <note> <para> - Currently, pruning of partitions during the planning of an - <command>UPDATE</command> or <command>DELETE</command> command is - implemented using the constraint exclusion method (however, it is - controlled by the <literal>enable_partition_pruning</literal> rather than - <literal>constraint_exclusion</literal>) &mdash; see the following section - for details and caveats that apply. - </para> - - <para> Execution-time partition pruning currently only occurs for the <literal>Append</literal> and <literal>MergeAppend</literal> node types. It is not yet implemented for the <literal>ModifyTable</literal> node @@ -3908,9 +3899,8 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <para> <firstterm>Constraint exclusion</firstterm> is a query optimization - technique similar to partition pruning. While it is primarily used - for partitioning implemented using the legacy inheritance method, it can be - used for other purposes, including with declarative partitioning. + technique similar to partition pruning. It is primarily used + for partitioning implemented using the legacy inheritance method. </para> <para> diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index f0c396530d..08657fcfd9 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2309,7 +2309,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); WRITE_UINT_FIELD(qual_security_level); - WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind); WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasDeletedRTEs); diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 738bb30848..ba944b2edc 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -36,6 +36,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" @@ -93,6 +94,9 @@ static void set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); +static PlannerInfo *adjust_inherit_target_child(PlannerInfo *root, + RelOptInfo *childrel, + AppendRelInfo *appinfo); static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, @@ -119,6 +123,8 @@ static void set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); +static void inheritance_make_rel_from_joinlist(PlannerInfo *root, + List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, pushdown_safety_info *safetyInfo); static bool recurse_pushdown_safe(Node *setOp, Query *topquery, @@ -217,13 +223,40 @@ make_one_rel(PlannerInfo *root, List *joinlist) /* * Generate access paths for the entire join tree. + * + * For UPDATE/DELETE on an inheritance parent, join paths should be + * generated for each child result rel separately. */ - rel = make_rel_from_joinlist(root, joinlist); + if (root->parse->resultRelation && + root->simple_rte_array[root->parse->resultRelation]->inh) + { + inheritance_make_rel_from_joinlist(root, joinlist); - /* - * The result should join all and only the query's base rels. - */ - Assert(bms_equal(rel->relids, root->all_baserels)); + /* + * There should be as many child source rels as there are child + * subroots. + */ + Assert(list_length(root->inh_target_child_roots) == + list_length(root->inh_target_child_rels)); + + /* + * Return the RelOptInfo of original target relation, although this + * doesn't really contain the final path. inheritance_planner + * from where we got here will generate the final path, but not + * through this RelOptInfo. + */ + rel = find_base_rel(root, root->parse->resultRelation); + } + else + { + rel = make_rel_from_joinlist(root, joinlist); + + /* + * The result should join all and only the query's base rels. + */ + Assert(bms_equal(rel->relids, root->all_baserels)); + + } return rel; } @@ -1001,6 +1034,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, ListCell *parentvars; ListCell *childvars; ListCell *lc; + PlannerInfo *subroot = root; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1029,11 +1063,19 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, * fields of childrel->reltarget; not clear if that would be useful. */ childrel->reltarget->exprs = (List *) - adjust_appendrel_attrs(root, + adjust_appendrel_attrs(subroot, (Node *) rel->reltarget->exprs, 1, &appinfo); /* + * If the parent is the result relation, we need a reltarget for the + * child relation that will be suitable to use the child also as the + * target relation. + */ + if (appinfo->parent_relid == root->parse->resultRelation) + subroot = adjust_inherit_target_child(root, childrel, appinfo); + + /* * We have to make child entries in the EquivalenceClass data * structures as well. This is needed either if the parent * participates in some eclass joins (because we will want to consider @@ -1045,7 +1087,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, * EquivalenceClass data structures. */ if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) - add_child_rel_equivalences(root, appinfo, rel, childrel); + add_child_rel_equivalences(subroot, appinfo, rel, childrel, + root != subroot); childrel->has_eclass_joins = rel->has_eclass_joins; /* @@ -1188,7 +1231,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, /* CE failed, so finish copying/modifying join quals. */ childrel->joininfo = (List *) - adjust_appendrel_attrs(root, + adjust_appendrel_attrs(subroot, (Node *) rel->joininfo, 1, &appinfo); @@ -1216,12 +1259,12 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, * consistency, do this before calling set_rel_size() for the child. */ if (root->glob->parallelModeOK && rel->consider_parallel) - set_rel_consider_parallel(root, childrel, childRTE); + set_rel_consider_parallel(subroot, childrel, childRTE); /* - * Compute the child's size. + * Compute the child's size using possibly modified subroot. */ - set_rel_size(root, childrel, childRTindex, childRTE); + set_rel_size(subroot, childrel, childRTindex, childRTE); /* * It is possible that constraint exclusion detected a contradiction @@ -1231,6 +1274,25 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, if (IS_DUMMY_REL(childrel)) continue; + /* + * If we modified subroot for the target inheritance case, add it + * to root->inh_target_child_roots. + */ + if (subroot != root) + { + root->inh_target_child_roots = + lappend(root->inh_target_child_roots, subroot); + + /* + * If the childrel itself was a partitioned table, its children + * would've been added into subroot. + */ + if (subroot->inh_target_child_roots != NIL) + root->inh_target_child_roots = + list_concat(root->inh_target_child_roots, + subroot->inh_target_child_roots); + } + /* We have at least one live child. */ has_live_children = true; @@ -1327,6 +1389,125 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, } /* + * adjust_inherit_target_child + * For an inheritance target child relation, this adjusts its + * reltarget so that it contains expressions suitable for processing as + * a target relation, creates a child PlannerInfo containing translated + * copy of the query and returns it. + * + * The child PlannerInfo reuses most of the parent PlannerInfo's fields + * unchanged, except unexpanded_tlist and processed_tlist are based on the + * child relation. + */ +static PlannerInfo * +adjust_inherit_target_child(PlannerInfo *root, RelOptInfo *childrel, + AppendRelInfo *appinfo) +{ + PlannerInfo *subroot; + List *tlist; + List *translated_exprs; + ListCell *lc; + + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + + /* + * We'd like to build the reltarget afresh; save the translated + * version of parent's expressions aside. + */ + translated_exprs = childrel->reltarget->exprs; + childrel->reltarget->exprs = NIL; + + /* Translate the original query's expressions to this child. */ + subroot = makeNode(PlannerInfo); + memcpy(subroot, root, sizeof(PlannerInfo)); + + /* + * Restore the unexpanded tlist for translation, so that child's + * query contains targetList numbered (resnos) per its own + * TupleDesc, which adjust_inherited_tlist ensures. + */ + root->parse->targetList = root->unexpanded_tlist; + subroot->parse = (Query *) adjust_appendrel_attrs(root, + (Node *) root->parse, + 1, &appinfo); + + /* + * Save subroot's targetlist so that childrel's own children can use it as + * unexpanded tlist. Must copy because subroot->parse->targetList will + * be modified soon. + */ + subroot->unexpanded_tlist = list_copy(subroot->parse->targetList); + + /* + * Apply planner's expansion of targetlist, such as adding various junk + * column, filling placeholder entries for dropped columns, etc., all of + * which occurs with the child's TupleDesc. + */ + tlist = preprocess_targetlist(subroot); + subroot->processed_tlist = tlist; + build_base_rel_tlists(subroot, tlist); + + /* + * Some of the expressions in parent's reltarget might not be in the + * child's freshly built reltarget expressions, because the latter only + * contains those attributes that are needed to be present in the top- + * level tlist (or ones that preprocess_targetlist thinks are needed to + * be in the tlist.) We may need other attributes such as those that + * are required for computing WHERE clauses, which are already computed + * for the parent during deconstruct_jointree processing of the original + * query. We've already got a translated copy of those attributes, from + * which pick only those that are not already present. + */ + foreach(lc, translated_exprs) + { + Expr *expr = lfirst(lc); + + if (!list_member(childrel->reltarget->exprs, expr)) + childrel->reltarget->exprs = lappend(childrel->reltarget->exprs, + expr); + } + + /* + * Set a few other fields of subroot. + * + * Reset inh_target_child_roots to not be same as parent root's so that + * the subroots for this child's own children (if any) don't end up in + * root parent's list. We'll eventually merge all entries into one list, + * but that's now now. + */ + subroot->inh_target_child_roots = NIL; + + /* + * Adjust all_baserels to replace the original target relation with the + * child target relation. Copy it before modifying though. + */ + subroot->all_baserels = bms_copy(root->all_baserels); + subroot->all_baserels = bms_del_member(subroot->all_baserels, + root->parse->resultRelation); + subroot->all_baserels = bms_add_member(subroot->all_baserels, + subroot->parse->resultRelation); + + /* + * Child root should get its own copy of ECs, because they'll be modified + * to replace parent EC expressions by child expressions in + * add_child_rel_equivalences. + */ + subroot->eq_classes = NIL; + foreach(lc, root->eq_classes) + { + EquivalenceClass *ec = lfirst(lc); + EquivalenceClass *new_ec = makeNode(EquivalenceClass); + + memcpy(new_ec, ec, sizeof(EquivalenceClass)); + new_ec->ec_members = list_copy(ec->ec_members); + subroot->eq_classes = lappend(subroot->eq_classes, new_ec); + } + + return subroot; +} + +/* * set_append_rel_pathlist * Build access paths for an "append relation" */ @@ -2624,6 +2805,131 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows) } /* + * inheritance_make_rel_from_joinlist + * Perform join planning for all non-dummy leaf inheritance children + * in their role as an UPDATE/DELETE query's target relation + * + * If a child relation is a partitioned table, its children are processed in + * turn by recursively calling this function. + */ +static void +inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; +#ifdef USE_ASSERT_CHECKING + Relids all_baserels; +#endif + + /* + * For UPDATE/DELETE queries, the top parent can only ever be a table. + * As a contrast, it could be a UNION ALL subquery in the case of SELECT. + */ + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + Assert(planner_rt_fetch(resultRelation, root)->rtekind == RTE_RELATION); + + /* Nothing to do. */ + if (IS_DUMMY_REL(find_base_rel(root, resultRelation ))) + return; + + foreach(lc, root->inh_target_child_roots) + { + PlannerInfo *subroot = lfirst(lc); + RelOptInfo *childrel; + AppendRelInfo *appinfo; + List *translated_joinlist; + + Assert(subroot->parse->resultRelation > 0); + childrel = find_base_rel(root, subroot->parse->resultRelation); + appinfo = root->append_rel_array[subroot->parse->resultRelation]; + + if (appinfo->parent_relid != root->parse->resultRelation) + continue; + + /* Ignore excluded/pruned children. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* + * Modify joinlist such that relations joined to the top parent rel + * appear to be joined to the child rel instead. Do the same for + * any SpecialJoinInfo structs. + */ + translated_joinlist = (List *) + adjust_appendrel_attrs(subroot, + (Node *) joinlist, + 1, &appinfo); + subroot->join_info_list = (List *) + adjust_appendrel_attrs(subroot, + (Node *) root->join_info_list, + 1, &appinfo); + + /* + * Sub-partitioned tables have to be processed recursively using the + * translated subroot as the parent, because AppendRelInfos link + * sub-partitions to their immediate parents, not the root partitioned + * table. + */ + if (childrel->part_scheme != NULL) + { + inheritance_make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * Add this child relation as a placeholder in the parent root's + * inh_target_child_rels so that inheritance_planner see same + * number of entries as inh_target_child_roots. + */ + root->inh_target_child_rels = + lappend(root->inh_target_child_rels, childrel); + + /* Also propagate this child's own children into parent's list. */ + if (subroot->inh_target_child_rels != NIL) + root->inh_target_child_rels = + list_concat(root->inh_target_child_rels, + subroot->inh_target_child_rels); + continue; + } + + /* + * Since we added the child rel directly into the join tree, we must + * modify it to be a "base" rel instead of an "other" rel, which the + * join planning code expects the relations being joined to be. + */ + childrel->reloptkind = RELOPT_BASEREL; + + Assert(subroot->join_rel_list == NIL); + Assert(subroot->join_rel_hash == NULL); + + /* Perform join planning and save the resulting RelOptInfo. */ + childrel = make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * Remember this child target rel. inheritance_planner will perform + * the remaining steps of planning for each child relation separately. + * Specifically, it will call grouping_planner on every + * RelOptInfo contained in the inh_target_child_rels list, each of + * which represents the source of tuples to be modified for a given + * target child rel. + */ + root->inh_target_child_rels = + lappend(root->inh_target_child_rels, childrel); +#ifdef USE_ASSERT_CHECKING + /* + * The following implements essentially the same Assert as in + * make_one_rel, our caller. + */ + all_baserels = bms_copy(root->all_baserels); + all_baserels = bms_del_member(all_baserels, + root->parse->resultRelation); + all_baserels = bms_add_member(all_baserels, + subroot->parse->resultRelation); + Assert(bms_equal(childrel->relids, all_baserels)); +#endif + } +} + +/* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. * diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index b22b36ec0e..029665b974 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -2104,12 +2104,18 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root, * * parent_rel and child_rel could be derived from appinfo, but since the * caller has already computed them, we might as well just pass them in. + * + * If 'child_is_target' is true then the child EC members *replace* the + * corresponding parent members. In that case, 'root' is the child target + * relation's dedicated PlannerInfo so it makes sense to remove the parent + * ECs altogether, because they're of no use. */ void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, - RelOptInfo *child_rel) + RelOptInfo *child_rel, + bool child_is_target) { ListCell *lc1; @@ -2117,6 +2123,8 @@ add_child_rel_equivalences(PlannerInfo *root, { EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc1); ListCell *lc2; + ListCell *prev; + ListCell *next; /* * If this EC contains a volatile expression, then generating child @@ -2134,12 +2142,18 @@ add_child_rel_equivalences(PlannerInfo *root, !bms_is_subset(parent_rel->relids, cur_ec->ec_relids)) continue; - foreach(lc2, cur_ec->ec_members) + prev = NULL; + for (lc2 = list_head(cur_ec->ec_members); lc2 != NULL; lc2 = next) { EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2); + next = lnext(lc2); + if (cur_em->em_is_const) + { + prev = lc2; continue; /* ignore consts here */ + } /* Does it reference parent_rel? */ if (bms_overlap(cur_em->em_relids, parent_rel->relids)) @@ -2177,10 +2191,17 @@ add_child_rel_equivalences(PlannerInfo *root, child_rel->relids); } + /* Delete the parent EC member. */ + if (child_is_target) + cur_ec->ec_members = list_delete_cell(cur_ec->ec_members, + lc2, prev); + (void) add_eq_member(cur_ec, child_expr, new_relids, new_nullable_relids, - true, cur_em->em_datatype); + !child_is_target, cur_em->em_datatype); } + else + prev = lc2; } } } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index da7a92081a..28c4b53fea 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -1998,12 +1998,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) /* * During setrefs.c, we'll need the grouping_map to fix up the cols lists * in GroupingFunc nodes. Save it for setrefs.c to use. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be grouping in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->grouping_map == NULL); root->grouping_map = grouping_map; @@ -2160,12 +2155,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) * with InitPlan output params. (We can't just do that locally in the * MinMaxAgg node, because path nodes above here may have Agg references * as well.) Save the mmaggregates list to tell setrefs.c to do that. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be aggregates in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->minmax_aggs == NIL); root->minmax_aggs = best_path->mmaggregates; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index c729a99f8b..3fd1476f71 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -37,6 +37,7 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "nodes/relation.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" @@ -126,7 +127,7 @@ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static void inheritance_planner(PlannerInfo *root); static void grouping_planner(PlannerInfo *root, bool inheritance_update, - double tuple_fraction); + RelOptInfo *planned_rel, double tuple_fraction); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, int *tleref_to_colnum_map); @@ -630,7 +631,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->grouping_map = NULL; root->minmax_aggs = NIL; root->qual_security_level = 0; - root->inhTargetKind = INHKIND_NONE; root->hasRecursion = hasRecursion; if (hasRecursion) root->wt_param_id = SS_assign_special_param(root); @@ -970,7 +970,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh) inheritance_planner(root); else - grouping_planner(root, false, tuple_fraction); + grouping_planner(root, false, NULL, tuple_fraction); /* * Capture the set of outer-level param IDs we have access to, for use in @@ -1146,12 +1146,19 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr) * inheritance set. * * We have to handle this case differently from cases where a source relation - * is an inheritance set. Source inheritance is expanded at the bottom of the - * plan tree (see allpaths.c), but target inheritance has to be expanded at - * the top. The reason is that for UPDATE, each target relation needs a - * different targetlist matching its own column set. Fortunately, - * the UPDATE/DELETE target can never be the nullable side of an outer join, - * so it's OK to generate the plan this way. + * is an inheritance set. That's mainly because for UPDATE, each target + * relation needs a different targetlist matching its own column set. So, we + * must modify the source scan/join path for each target relation such that it + * produces the desired target list. + * + * The source scan/join paths for individual target relations are still + * created in allpaths.c by first expanding the inheritance in the usual way + * by set_append_rel_size, followed by join planning for each target relation + * separately in make_one_rel. Finally, we apply grouping_planner here to each + * child scan/join path so that it produces the desired targetlist. + * + * Fortunately, the UPDATE/DELETE target can never be the nullable side of an + * outer join, so it's OK to generate the plan this way. * * Returns nothing; the useful output is in the Paths we attach to * the (UPPERREL_FINAL, NULL) upperrel stored in *root. @@ -1164,14 +1171,8 @@ inheritance_planner(PlannerInfo *root) { Query *parse = root->parse; int top_parentRTindex = parse->resultRelation; - Bitmapset *subqueryRTindexes; - Bitmapset *modifiableARIindexes; int nominalRelation = -1; Index rootRelation = 0; - List *final_rtable = NIL; - int save_rel_array_size = 0; - RelOptInfo **save_rel_array = NULL; - AppendRelInfo **save_append_rel_array = NULL; List *subpaths = NIL; List *subroots = NIL; List *resultRelations = NIL; @@ -1179,70 +1180,59 @@ inheritance_planner(PlannerInfo *root) List *returningLists = NIL; List *rowMarks; RelOptInfo *final_rel; - ListCell *lc; - Index rti; + ListCell *lc1, + *lc2; RangeTblEntry *parent_rte; - PlannerInfo *parent_root; - Query *parent_parse; - Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); - PlannerInfo **parent_roots = NULL; + List *tlist; + standard_qp_extra qp_extra; + RelOptInfo *planned_rel; + /* Inheritance is never used for insert. */ Assert(parse->commandType != CMD_INSERT); + parent_rte = planner_rt_fetch(top_parentRTindex, root); /* - * We generate a modified instance of the original Query for each target - * relation, plan that, and put all the plans into a list that will be - * controlled by a single ModifyTable node. All the instances share the - * same rangetable, but each instance must have its own set of subquery - * RTEs within the finished rangetable because (1) they are likely to get - * scribbled on during planning, and (2) it's not inconceivable that - * subqueries could get planned differently in different cases. We need - * not create duplicate copies of other RTE kinds, in particular not the - * target relations, because they don't have either of those issues. Not - * having to duplicate the target relations is important because doing so - * (1) would result in a rangetable of length O(N^2) for N targets, with - * at least O(N^3) work expended here; and (2) would greatly complicate - * management of the rowMarks list. + * Generate the access paths for all relations mentioned in the query, + * including the target inheritance set. When doing the join planning, + * references in the join tree to the original target relation that's the + * root parent of the inheritance tree is replaced by each of its + * inheritance children and the resulting joinrel RelOptInfo's are + * added to root->inh_target_child_rels. * - * To begin with, generate a bitmapset of the relids of the subquery RTEs. + * Final planning steps (grouping_planner) are applied to the best path + * of each of those child joinrels using a modified instance of the + * original query for a given child target rel. All the paths so generated + * are put into a list that will be controlled by a single ModifyTable + * node. All the instances share the same rangetable. */ - subqueryRTindexes = NULL; - rti = 1; - foreach(lc, parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - if (rte->rtekind == RTE_SUBQUERY) - subqueryRTindexes = bms_add_member(subqueryRTindexes, rti); - rti++; - } /* - * Next, we want to identify which AppendRelInfo items contain references - * to any of the aforesaid subquery RTEs. These items will need to be - * copied and modified to adjust their subquery references; whereas the - * other ones need not be touched. It's worth being tense over this - * because we can usually avoid processing most of the AppendRelInfo - * items, thereby saving O(N^2) space and time when the target is a large - * inheritance tree. We can identify AppendRelInfo items by their - * child_relid, since that should be unique within the list. + * Save the unexpanded version of the query's targetlist to be used below + * for passing to grouping_planner for each child target relation. */ - modifiableARIindexes = NULL; - if (subqueryRTindexes != NULL) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); + root->unexpanded_tlist = list_copy(root->parse->targetList); - if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) || - bms_is_member(appinfo->child_relid, subqueryRTindexes) || - bms_overlap(pull_varnos((Node *) appinfo->translated_vars), - subqueryRTindexes)) - modifiableARIindexes = bms_add_member(modifiableARIindexes, - appinfo->child_relid); - } + /* Do the scan/join planning. */ + tlist = preprocess_targetlist(root); + root->processed_tlist = tlist; + qp_extra.tlist = tlist; + qp_extra.activeWindows = qp_extra.groupClause = NIL; + planned_rel = query_planner(root, tlist, standard_qp_callback, &qp_extra); + + /* + * If it turned out during query planning that all the children are dummy + * (pruned or excluded by constraints), no need to do the steps below. + * Let grouping_planner finish up the final path. + */ + if (IS_DUMMY_REL(planned_rel)) + { + grouping_planner(root, false, planned_rel, 0.0); + return; } + Assert(planned_rel->relid == top_parentRTindex); + Assert(planned_rel->reloptkind == RELOPT_BASEREL); + /* * If the parent RTE is a partitioned table, we should use that as the * nominal target relation, because the RTEs added for partitioned tables @@ -1250,7 +1240,6 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1258,95 +1247,40 @@ inheritance_planner(PlannerInfo *root) } /* - * The PlannerInfo for each child is obtained by translating the relevant - * members of the PlannerInfo for its immediate parent, which we find - * using the parent_relid in its AppendRelInfo. We save the PlannerInfo - * for each parent in an array indexed by relid for fast retrieval. Since - * the maximum number of parents is limited by the number of RTEs in the - * query, we use that number to allocate the array. An extra entry is - * needed since relids start from 1. + * Get on with finalizing the path for each child target relation by + * calling grouping_planner on its joinrel. Note that we're restoring + * the query's targetlist to the original one for grouping_planner's + * targetlist expansion steps to perform the expansion with individual + * child descriptors. */ - parent_roots = (PlannerInfo **) palloc0((list_length(parse->rtable) + 1) * - sizeof(PlannerInfo *)); - parent_roots[top_parentRTindex] = root; - - /* - * And now we can get on with generating a plan for each child table. - */ - foreach(lc, root->append_rel_list) + forboth(lc1, root->inh_target_child_roots, + lc2, root->inh_target_child_rels) { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - PlannerInfo *subroot; + PlannerInfo *subroot = lfirst(lc1); + RelOptInfo *childrel = lfirst(lc2); + AppendRelInfo *appinfo; RangeTblEntry *child_rte; RelOptInfo *sub_final_rel; Path *subpath; - /* append_rel_list contains all append rels; ignore others */ - if (!bms_is_member(appinfo->parent_relid, parent_relids)) - continue; - - /* - * expand_inherited_rtentry() always processes a parent before any of - * that parent's children, so the parent_root for this relation should - * already be available. - */ - parent_root = parent_roots[appinfo->parent_relid]; - Assert(parent_root != NULL); - parent_parse = parent_root->parse; - - /* - * We need a working copy of the PlannerInfo so that we can control - * propagation of information back to the main copy. - */ - subroot = makeNode(PlannerInfo); - memcpy(subroot, parent_root, sizeof(PlannerInfo)); - - /* - * Generate modified query with this rel as target. We first apply - * adjust_appendrel_attrs, which copies the Query and changes - * references to the parent RTE to refer to the current child RTE, - * then fool around with subquery RTEs. - */ - subroot->parse = (Query *) - adjust_appendrel_attrs(parent_root, - (Node *) parent_parse, - 1, &appinfo); + appinfo = root->append_rel_array[subroot->parse->resultRelation]; /* * If there are securityQuals attached to the parent, move them to the * child rel (they've already been transformed properly for that). */ - parent_rte = rt_fetch(appinfo->parent_relid, subroot->parse->rtable); - child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable); + parent_rte = planner_rt_fetch(appinfo->parent_relid, subroot); + child_rte = planner_rt_fetch(appinfo->child_relid, subroot); child_rte->securityQuals = parent_rte->securityQuals; parent_rte->securityQuals = NIL; /* - * Mark whether we're planning a query to a partitioned table or an - * inheritance parent. + * Ignore a partitioned child. Instead, the paths of its children will + * be added to subpaths. */ - subroot->inhTargetKind = - (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED; - - /* - * If this child is further partitioned, remember it as a parent. - * Since a partitioned table does not have any data, we don't need to - * create a plan for it, and we can stop processing it here. We do, - * however, need to remember its modified PlannerInfo for use when - * processing its children, since we'll update their varnos based on - * the delta from immediate parent to child, not from top to child. - * - * Note: a very non-obvious point is that we have not yet added - * duplicate subquery RTEs to the subroot's rtable. We mustn't, - * because then its children would have two sets of duplicates, - * confusing matters. - */ - if (child_rte->inh) + if (childrel->part_scheme) { - Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE); - parent_relids = bms_add_member(parent_relids, appinfo->child_relid); - parent_roots[appinfo->child_relid] = subroot; - + Assert(child_rte->inh); continue; } @@ -1374,111 +1308,8 @@ inheritance_planner(PlannerInfo *root) if (nominalRelation < 0) nominalRelation = appinfo->child_relid; - /* - * The rowMarks list might contain references to subquery RTEs, so - * make a copy that we can apply ChangeVarNodes to. (Fortunately, the - * executor doesn't need to see the modified copies --- we can just - * pass it the original rowMarks list.) - */ - subroot->rowMarks = copyObject(parent_root->rowMarks); - - /* - * The append_rel_list likewise might contain references to subquery - * RTEs (if any subqueries were flattenable UNION ALLs). So prepare - * to apply ChangeVarNodes to that, too. As explained above, we only - * want to copy items that actually contain such references; the rest - * can just get linked into the subroot's append_rel_list. - * - * If we know there are no such references, we can just use the outer - * append_rel_list unmodified. - */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - subroot->append_rel_list = NIL; - foreach(lc2, parent_root->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, modifiableARIindexes)) - appinfo2 = copyObject(appinfo2); - - subroot->append_rel_list = lappend(subroot->append_rel_list, - appinfo2); - } - } - - /* - * Add placeholders to the child Query's rangetable list to fill the - * RT indexes already reserved for subqueries in previous children. - * These won't be referenced, so there's no need to make them very - * valid-looking. - */ - while (list_length(subroot->parse->rtable) < list_length(final_rtable)) - subroot->parse->rtable = lappend(subroot->parse->rtable, - makeNode(RangeTblEntry)); - - /* - * If this isn't the first child Query, generate duplicates of all - * subquery RTEs, and adjust Var numbering to reference the - * duplicates. To simplify the loop logic, we scan the original rtable - * not the copy just made by adjust_appendrel_attrs; that should be OK - * since subquery RTEs couldn't contain any references to the target - * rel. - */ - if (final_rtable != NIL && subqueryRTindexes != NULL) - { - ListCell *lr; - - rti = 1; - foreach(lr, parent_parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lr); - - if (bms_is_member(rti, subqueryRTindexes)) - { - Index newrti; - - /* - * The RTE can't contain any references to its own RT - * index, except in its securityQuals, so we can save a - * few cycles by applying ChangeVarNodes to the rest of - * the rangetable before we append the RTE to it. - */ - newrti = list_length(subroot->parse->rtable) + 1; - ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); - ChangeVarNodes((Node *) subroot->rowMarks, rti, newrti, 0); - /* Skip processing unchanging parts of append_rel_list */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - foreach(lc2, subroot->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, - modifiableARIindexes)) - ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); - } - } - rte = copyObject(rte); - ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); - subroot->parse->rtable = lappend(subroot->parse->rtable, - rte); - } - rti++; - } - } - - /* There shouldn't be any OJ info to translate, as yet */ - Assert(subroot->join_info_list == NIL); - /* and we haven't created PlaceHolderInfos, either */ - Assert(subroot->placeholder_list == NIL); - - /* Generate Path(s) for accessing this result relation */ - grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ ); + /* Apply the top-level targetlist to childrel's path. */ + grouping_planner(subroot, true, childrel, 0.0); /* * Select cheapest path in case there's more than one. We always run @@ -1490,45 +1321,10 @@ inheritance_planner(PlannerInfo *root) subpath = sub_final_rel->cheapest_total_path; /* - * If this child rel was excluded by constraint exclusion, exclude it - * from the result plan. + * child rel cannot be empty, or inheritance_make_rel_from_joinlist + * wouldn't have put it in the list to begin with. */ - if (IS_DUMMY_PATH(subpath)) - continue; - - /* - * If this is the first non-excluded child, its post-planning rtable - * becomes the initial contents of final_rtable; otherwise, append - * just its modified subquery RTEs to final_rtable. - */ - if (final_rtable == NIL) - final_rtable = subroot->parse->rtable; - else - final_rtable = list_concat(final_rtable, - list_copy_tail(subroot->parse->rtable, - list_length(final_rtable))); - - /* - * We need to collect all the RelOptInfos from all child plans into - * the main PlannerInfo, since setrefs.c will need them. We use the - * last child's simple_rel_array (previous ones are too short), so we - * have to propagate forward the RelOptInfos that were already built - * in previous children. - */ - Assert(subroot->simple_rel_array_size >= save_rel_array_size); - for (rti = 1; rti < save_rel_array_size; rti++) - { - RelOptInfo *brel = save_rel_array[rti]; - - if (brel) - subroot->simple_rel_array[rti] = brel; - } - save_rel_array_size = subroot->simple_rel_array_size; - save_rel_array = subroot->simple_rel_array; - save_append_rel_array = subroot->append_rel_array; - - /* Make sure any initplans from this rel get into the outer list */ - root->init_plans = subroot->init_plans; + Assert(!IS_DUMMY_PATH(subpath)); /* Build list of sub-paths */ subpaths = lappend(subpaths, subpath); @@ -1560,36 +1356,6 @@ inheritance_planner(PlannerInfo *root) */ /* - * If we managed to exclude every child rel, return a dummy plan; it - * doesn't even need a ModifyTable node. - */ - if (subpaths == NIL) - { - set_dummy_rel_pathlist(final_rel); - return; - } - - /* - * Put back the final adjusted rtable into the master copy of the Query. - * (We mustn't do this if we found no non-excluded children.) - */ - parse->rtable = final_rtable; - root->simple_rel_array_size = save_rel_array_size; - root->simple_rel_array = save_rel_array; - root->append_rel_array = save_append_rel_array; - - /* Must reconstruct master's simple_rte_array, too */ - root->simple_rte_array = (RangeTblEntry **) - palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *)); - rti = 1; - foreach(lc, final_rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - root->simple_rte_array[rti++] = rte; - } - - /* * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will * have dealt with fetching non-locked marked rows, else we need to have * ModifyTable do that. @@ -1629,6 +1395,12 @@ inheritance_planner(PlannerInfo *root) * (inheritance_planner will create a single ModifyTable node covering all the * target tables.) * + * If non-NULL, planned_rel is a RelOptInfo containing paths for the query's + * top-level joinrel, which the caller produced by itself. In that case, this + * function only needs to adjust the targetlist of its cheapest_total_path. + * The only caller that may pass such a RelOptInfo currently is + * inheritance_planner. + * * tuple_fraction is the fraction of tuples we expect will be retrieved. * tuple_fraction is interpreted as follows: * 0: expect all tuples to be retrieved (normal case) @@ -1647,6 +1419,7 @@ inheritance_planner(PlannerInfo *root) */ static void grouping_planner(PlannerInfo *root, bool inheritance_update, + RelOptInfo *planned_rel, double tuple_fraction) { Query *parse = root->parse; @@ -1659,7 +1432,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, List *final_targets; List *final_targets_contain_srfs; bool final_target_parallel_safe; - RelOptInfo *current_rel; + RelOptInfo *current_rel = planned_rel; RelOptInfo *final_rel; ListCell *lc; @@ -1699,6 +1472,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * special work for recursive unions is the responsibility of * plan_set_operations. */ + Assert(current_rel == NULL); current_rel = plan_set_operations(root); /* @@ -1788,17 +1562,26 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, parse->groupClause = preprocess_groupclause(root, NIL); } - /* Preprocess targetlist */ - tlist = preprocess_targetlist(root); - /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation so - * that we can transfer its decoration (resnames etc) to the topmost - * tlist of the finished Plan. + * Preprocess targetlist, if needed. If the caller has already done + * query planning, root->processed_tlist already contain the desired + * targetlist. */ - root->processed_tlist = tlist; + if (planned_rel == NULL) + { + tlist = preprocess_targetlist(root); + + /* + * We are now done hacking up the query's targetlist. Most of the + * remaining planning work will be done with the PathTarget + * representation of tlists, but save aside the full representation + * so that we can transfer its decoration (resnames etc) to the + * topmost tlist of the finished Plan. + */ + root->processed_tlist = tlist; + } + else + tlist = root->processed_tlist; /* * Collect statistics about aggregates for estimating costs, and mark @@ -1878,8 +1661,9 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * We also generate (in standard_qp_callback) pathkey representations * of the query's sort clause, distinct clause, etc. */ - current_rel = query_planner(root, tlist, - standard_qp_callback, &qp_extra); + if (current_rel == NULL) + current_rel = query_planner(root, tlist, + standard_qp_callback, &qp_extra); /* * Convert the query's result tlist into PathTarget format. diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index cd6e11904e..78baec00dc 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -917,7 +917,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->grouping_map = NULL; subroot->minmax_aggs = NIL; subroot->qual_security_level = 0; - subroot->inhTargetKind = INHKIND_NONE; subroot->hasRecursion = false; subroot->wt_param_id = -1; subroot->non_recursive_path = NULL; diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 2a1c1cb2e1..134e7bafd9 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -2250,8 +2250,39 @@ adjust_appendrel_attrs_mutator(Node *node, context->appinfos); return (Node *) phv; } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index a570ac0aab..a0fed5be42 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1264,36 +1264,6 @@ get_relation_constraints(PlannerInfo *root, } } - /* - * Append partition predicates, if any. - * - * For selects, partition pruning uses the parent table's partition bound - * descriptor, instead of constraint exclusion which is driven by the - * individual partition's partition constraint. - */ - if (enable_partition_pruning && root->parse->commandType != CMD_SELECT) - { - List *pcqual = RelationGetPartitionQual(relation); - - if (pcqual) - { - /* - * Run the partition quals through const-simplification similar to - * check constraints. We skip canonicalize_qual, though, because - * partition quals should be in canonical form already; also, - * since the qual is in implicit-AND format, we'd have to - * explicitly convert it to explicit-AND format and back again. - */ - pcqual = (List *) eval_const_expressions(root, (Node *) pcqual); - - /* Fix Vars to have the desired varno */ - if (varno != 1) - ChangeVarNodes((Node *) pcqual, 1, varno, 0); - - result = list_concat(result, pcqual); - } - } - heap_close(relation, NoLock); return result; @@ -1420,31 +1390,15 @@ relation_excluded_by_constraints(PlannerInfo *root, switch (constraint_exclusion) { case CONSTRAINT_EXCLUSION_OFF: - - /* - * Don't prune if feature turned off -- except if the relation is - * a partition. While partprune.c-style partition pruning is not - * yet in use for all cases (update/delete is not handled), it - * would be a UI horror to use different user-visible controls - * depending on such a volatile implementation detail. Therefore, - * for partitioned tables we use enable_partition_pruning to - * control this behavior. - */ - if (root->inhTargetKind == INHKIND_PARTITIONED) - break; return false; case CONSTRAINT_EXCLUSION_PARTITION: /* * When constraint_exclusion is set to 'partition' we only handle - * OTHER_MEMBER_RELs, or BASERELs in cases where the result target - * is an inheritance parent or a partitioned table. + * OTHER_MEMBER_RELs. */ - if ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) && - !(rel->reloptkind == RELOPT_BASEREL && - root->inhTargetKind != INHKIND_NONE && - rel->relid == root->parse->resultRelation)) + if (rel->reloptkind != RELOPT_OTHER_MEMBER_REL) return false; break; diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 6fd24203dd..2ad3dc4711 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -319,9 +319,6 @@ typedef struct PlannerInfo Index qual_security_level; /* minimum security_level for quals */ /* Note: qual_security_level is zero if there are no securityQuals */ - InheritanceKind inhTargetKind; /* indicates if the target relation is an - * inheritance child or partition or a - * partitioned table */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */ bool hasDeletedRTEs; /* true if any RTE was deleted from jointree */ @@ -343,6 +340,34 @@ typedef struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* + * The following fields are set during query planning portion of an + * inherited UPDATE/DEELETE operation. + */ + + /* + * This stores the original version of the query's targetlist that's + * not modified by the planner. + */ + List *unexpanded_tlist; + + /* + * List containing a PlannerInfo corresponding to each child target rel. + * Content of each PlannerInfo is same as the parent PlannerInfo, except + * for the parse tree which is a translated copy of the parent's parse + * tree. + */ + List *inh_target_child_roots; + + /* + * RelOptInfos corresponding to each child target rel. For leaf children, + * it's the RelOptInfo representing the output of make_rel_from_joinlist() + * called with the parent rel in the original join tree replaced by a + * given leaf child. For non-leaf children, it's the baserel RelOptInfo + * itself, left as a placeholder. + */ + List *inh_target_child_rels; } PlannerInfo; diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index cafde307ad..7aa1b6e17f 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -161,7 +161,8 @@ extern EquivalenceClass *match_eclasses_to_foreign_key_col(PlannerInfo *root, extern void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, - RelOptInfo *child_rel); + RelOptInfo *child_rel, + bool child_is_target); extern List *generate_implied_equalities_for_column(PlannerInfo *root, RelOptInfo *rel, ec_matches_callback_type callback, diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index 3ba3aaf2d8..a539280851 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1781,7 +1781,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_1 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_1 -> Nested Loop Semi Join @@ -1789,7 +1789,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_2 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_2 (28 rows) -- 2.11.0
v8-0002-Store-inheritance-root-parent-index-in-otherrel-s.patch (text/plain, 2.5K) download From 2b7f9f7f9f8d8fcbd493eb03cdfa9efdd56d6ed7 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 26 Oct 2018 16:45:59 +0900 Subject: [PATCH v8 2/6] Store inheritance root parent index in otherrel's RelOptInfo Although it's set by build_simple_rel, it's not being used by any code yet. --- src/backend/nodes/outfuncs.c | 1 + src/backend/optimizer/util/relnode.c | 14 ++++++++++++++ src/include/nodes/relation.h | 4 ++++ 3 files changed, 19 insertions(+) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 08657fcfd9..1d7c420e75 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2370,6 +2370,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_BOOL_FIELD(consider_partitionwise_join); WRITE_BITMAPSET_FIELD(top_parent_relids); WRITE_NODE_FIELD(partitioned_child_rels); + WRITE_UINT_FIELD(inh_root_parent); } static void diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 39f5729b91..29ba19349f 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -215,9 +215,23 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->top_parent_relids = parent->top_parent_relids; else rel->top_parent_relids = bms_copy(parent->relids); + + /* + * For inheritance child relations, we also set inh_root_parent. + * Note that 'parent' might itself be a child (a sub-partitioned + * partition), in which case we simply use its value of + * inh_root_parent. + */ + if (parent->rtekind == RTE_RELATION) + rel->inh_root_parent = parent->inh_root_parent > 0 ? + parent->inh_root_parent : + parent->relid; } else + { rel->top_parent_relids = NULL; + rel->inh_root_parent = 0; + } /* Check type of rtable entry */ switch (rte->rtekind) diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 2ad3dc4711..94f14019bd 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -728,6 +728,10 @@ typedef struct RelOptInfo List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ + + Index inh_root_parent; /* For otherrels, this is the RT index of + * inheritance table mentioned in the query + * from which this relation originated */ } RelOptInfo; /* -- 2.11.0
v8-0003-Lazy-creation-of-RTEs-for-inheritance-children.patch (text/plain, 84.9K) download From ad19486433a929f661d74d3b3f51eb8cd86f3d58 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 18:14:13 +0900 Subject: [PATCH v8 3/6] Lazy creation of RTEs for inheritance children Currently, expand_inherited_tables called from subquery_planner opens all inheritance child tables and adds them to the query's range table and PlannerInfo in the form of AppendRelInfo's initially and later RelOptInfo's. For partitioning, that's pretty wasteful, because it's possible to determine only the partitions that will need to be scanned using partition pruning, which doesn't require opening the partitions themselves. However, the point at which the above processing happens is too early to perform partition pruning. This commit rearranges things within the planner so that inheritance child relations can be added to the Query and PlannerInfo during make_one_rel, just before set_base_rel_sizes is called. Although the late initialization approach only benefits the partitioning case, it seems undesirable to do it only for partitioned tables, because it means adding code to handle partitions specially at various places within the planner. So, *all* inheritance parent tables are expanded in make_one_rel. All unpruned partitions are added to Query in the form of their RangeTblEntry's being added to the range table and to PlannerInfo in the form of an AppendRelInfo and a RelOptInfo for each. For partitioning, although we don't create a RangeTblEntry and RelOptInfo for pruned partitions at make_one_rel time, partitionwise join code relies on the fact that even though partitions may have been pruned, they'd still own a RelOptInfo to handle the outer join case where the pruned partition appears on the nullable side of join. Partitionwise join code deals with that by allocating dummy RelOptInfos for pruned partitions that are based mostly on their parent's properties. There are some regression test diffs: 1. Caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child, as seen in the partition_aggregate.out test output. 2. Those in postgres_fdw.out are caused by the fact that junk columns required for row marking are added to reltarget->exprs later than user columns, because the row marking junk columns arent added until after the inheritance is expanded which as of this commit is later than it used to be. --- contrib/postgres_fdw/expected/postgres_fdw.out | 32 +- src/backend/optimizer/path/allpaths.c | 219 +----- src/backend/optimizer/path/joinrels.c | 62 ++ src/backend/optimizer/plan/initsplan.c | 58 -- src/backend/optimizer/plan/planner.c | 60 +- src/backend/optimizer/plan/setrefs.c | 6 + src/backend/optimizer/prep/preptlist.c | 15 +- src/backend/optimizer/prep/prepunion.c | 851 +++++++++++++++------- src/backend/optimizer/util/Makefile | 2 +- src/backend/optimizer/util/plancat.c | 40 +- src/backend/optimizer/util/relnode.c | 80 +- src/backend/partitioning/partprune.c | 43 +- src/include/nodes/relation.h | 8 + src/include/optimizer/pathnode.h | 1 + src/include/optimizer/plancat.h | 2 +- src/include/optimizer/prep.h | 9 +- src/include/partitioning/partprune.h | 2 +- src/test/regress/expected/partition_aggregate.out | 4 +- 18 files changed, 882 insertions(+), 612 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index e653c302be..bbda5adc55 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7062,15 +7062,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7100,15 +7100,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7137,15 +7137,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid @@ -7155,15 +7155,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (39 rows) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index ba944b2edc..d44d034f24 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -44,7 +44,6 @@ #include "optimizer/var.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -180,6 +179,16 @@ make_one_rel(PlannerInfo *root, List *joinlist) set_base_rel_consider_startup(root); /* + * Expand RT entries that represent inherited or partitioned tables. + * This will perform partition pruning on partitioned tables in the + * original range table and also recursively on any child partitioned + * tables that were added by the expansion of the original parent(s). + * As new entries are added to the range table, various arrays in the + * PlannerInfo will be expanded accordingly. + */ + expand_inherited_tables(root); + + /* * Compute size estimates and consider_parallel flags for each base rel. */ set_base_rel_sizes(root); @@ -404,7 +413,11 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, */ set_dummy_rel_pathlist(rel); } - else if (rte->inh) + /* + * expand_inherited_tables may have proved that the relation is empty, so + * check if it's so. + */ + else if (rte->inh && !IS_DUMMY_REL(rel)) { /* It's an "append relation", process accordingly */ set_append_rel_size(root, rel, rti, rte); @@ -957,8 +970,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -966,32 +977,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Assert(IS_SIMPLE_REL(rel)); /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, we will bubble up - * the indexes of partitioned relations that appear down in the tree, so - * that when we've created Paths for all the children, the root - * partitioned table's list will contain all such indexes. - */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - rel->partitioned_child_rels = list_make1_int(rti); - - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - - /* * If this is a partitioned baserel, set the consider_partitionwise_join * flag; currently, we only consider partitionwise joins with the baserel * if its targetlist doesn't contain a whole-row Var. @@ -1028,12 +1013,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; ListCell *parentvars; ListCell *childvars; - ListCell *lc; PlannerInfo *subroot = root; /* append_rel_list contains all append rels; ignore others */ @@ -1042,18 +1023,34 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo is created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); /* - * Copy/Modify targetlist. Even if this child is deemed empty, we need - * its targetlist in case it falls on nullable side in a child-join - * because of partitionwise join. + * Child relation may have be marked dummy if build_append_child_rel + * found self-contradictory quals. + */ + if (IS_DUMMY_REL(childrel)) + continue; + + if (relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* + * This child need not be scanned, so we can omit it from the + * appendrel. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Copy/Modify targetlist. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -1091,144 +1088,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, root != subroot); childrel->has_eclass_joins = rel->has_eclass_joins; - /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* - * This child need not be scanned, so we can omit it from the - * appendrel. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - /* CE failed, so finish copying/modifying join quals. */ childrel->joininfo = (List *) adjust_appendrel_attrs(subroot, @@ -1442,9 +1301,10 @@ adjust_inherit_target_child(PlannerInfo *root, RelOptInfo *childrel, /* * Apply planner's expansion of targetlist, such as adding various junk * column, filling placeholder entries for dropped columns, etc., all of - * which occurs with the child's TupleDesc. + * which occurs with the child's TupleDesc. Since inheritance has already + * been expanded (we wouldn't be here otherwise), pass true. */ - tlist = preprocess_targetlist(subroot); + tlist = preprocess_targetlist(subroot, true); subroot->processed_tlist = tlist; build_base_rel_tlists(subroot, tlist); @@ -2842,6 +2702,7 @@ inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) Assert(subroot->parse->resultRelation > 0); childrel = find_base_rel(root, subroot->parse->resultRelation); + Assert(childrel != NULL); appinfo = root->append_rel_array[subroot->parse->resultRelation]; if (appinfo->parent_relid != root->parse->resultRelation) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index d3d21fed5d..da0831de4e 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -20,6 +20,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/prep.h" +#include "optimizer/tlist.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -46,6 +47,9 @@ static void try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, List *parent_restrictlist); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + int partidx); /* @@ -1376,6 +1380,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts); + if (child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts); + /* We should never try to join two overlapping sets of rels. */ Assert(!bms_overlap(child_rel1->relids, child_rel2->relids)); child_joinrelids = bms_union(child_rel1->relids, child_rel2->relids); @@ -1582,3 +1591,56 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx) +{ + RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + /* Pass parent's info as for both the parent rel and child rel. */ + AppendRelInfo *appinfo = make_append_rel_info(parent, parentrte, + parent->tupdesc, + parentrte->relid, + parent->reltype, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 01335db511..7636aa82c4 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -616,64 +616,6 @@ create_lateral_join_info(PlannerInfo *root) bms_add_member(brel2->lateral_referencers, rti); } } - - /* - * Lastly, propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - */ - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - RangeTblEntry *brte = root->simple_rte_array[rti]; - - /* - * Skip empty slots. Also skip non-simple relations i.e. dead - * relations. - */ - if (brel == NULL || !IS_SIMPLE_REL(brel)) - continue; - - /* - * In the case of table inheritance, the parent RTE is directly linked - * to every child table via an AppendRelInfo. In the case of table - * partitioning, the inheritance hierarchy is expanded one level at a - * time rather than flattened. Therefore, an other member rel that is - * a partitioned table may have children of its own, and must - * therefore be marked with the appropriate lateral info so that those - * children eventually get marked also. - */ - Assert(brte); - if (brel->reloptkind == RELOPT_OTHER_MEMBER_REL && - (brte->rtekind != RTE_RELATION || - brte->relkind != RELKIND_PARTITIONED_TABLE)) - continue; - - if (brte->inh) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); - RelOptInfo *childrel; - - if (appinfo->parent_relid != rti) - continue; - childrel = root->simple_rel_array[appinfo->child_relid]; - Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - Assert(childrel->direct_lateral_relids == NULL); - childrel->direct_lateral_relids = brel->direct_lateral_relids; - Assert(childrel->lateral_relids == NULL); - childrel->lateral_relids = brel->lateral_relids; - Assert(childrel->lateral_referencers == NULL); - childrel->lateral_referencers = brel->lateral_referencers; - } - } - } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 3fd1476f71..7f5a1865ba 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -23,6 +23,7 @@ #include "access/sysattr.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -699,27 +700,24 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } /* * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). + * pullup (so that all non-inherited RTEs are present). */ preprocess_rowmarks(root); /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain base relations not joins, so it's OK (and marginally more - * efficient) to do it after checking for join RTEs. We must do it after - * pulling up subqueries, else we'd fail to handle inherited tables in - * subqueries. - */ - expand_inherited_tables(root); - - /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -1212,8 +1210,11 @@ inheritance_planner(PlannerInfo *root) */ root->unexpanded_tlist = list_copy(root->parse->targetList); - /* Do the scan/join planning. */ - tlist = preprocess_targetlist(root); + /* + * Do the scan/join planning. We haven't expanded inheritance yet, so + * pass false. + */ + tlist = preprocess_targetlist(root, false); root->processed_tlist = tlist; qp_extra.tlist = tlist; qp_extra.activeWindows = qp_extra.groupClause = NIL; @@ -1222,9 +1223,10 @@ inheritance_planner(PlannerInfo *root) /* * If it turned out during query planning that all the children are dummy * (pruned or excluded by constraints), no need to do the steps below. - * Let grouping_planner finish up the final path. + * For regular inheritance case, it's possible that we only need to modify + * the parent table. Let grouping_planner finish up the final path. */ - if (IS_DUMMY_REL(planned_rel)) + if (!parent_rte->inh || IS_DUMMY_REL(planned_rel)) { grouping_planner(root, false, planned_rel, 0.0); return; @@ -1569,7 +1571,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, */ if (planned_rel == NULL) { - tlist = preprocess_targetlist(root); + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root, false); /* * We are now done hacking up the query's targetlist. Most of the @@ -1666,6 +1669,15 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, standard_qp_callback, &qp_extra); /* + * Query planning may have added some columns to the top-level tlist, + * which happens when there are row marks applied to inheritance + * parent relations (additional junk columns needed for applying row + * marks are added after expanding inheritance.) + */ + if (list_length(tlist) < list_length(root->processed_tlist)) + tlist = root->processed_tlist; + + /* * Convert the query's result tlist into PathTarget format. * * Note: it's desirable to not do this till after query_planner(), @@ -2352,7 +2364,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2377,7 +2389,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } @@ -6757,6 +6769,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -6858,6 +6874,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 6d6ef1c376..454870609a 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -326,6 +326,12 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) UPPERREL_FINAL, NULL))) add_rtes_to_flat_rtable(rel->subroot, true); } + /* + * A NULL rel also means an unplanned subquery rte, so apply + * flatten_unplanned_rtes. + */ + else + flatten_unplanned_rtes(glob, rte); } rti++; } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 8603feef2b..0747403acd 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -65,9 +65,13 @@ static List *expand_targetlist(List *tlist, int command_type, * * As a side effect, if there's an ON CONFLICT UPDATE clause, its targetlist * is also preprocessed (and updated in-place). + * + * inheritance_expanded specifies whether inheritance tables are expanded + * as of calling this function. Caller must specify it because that affects + * which row marking related junk columns get added to the targetlist. */ List * -preprocess_targetlist(PlannerInfo *root) +preprocess_targetlist(PlannerInfo *root, bool inheritance_expanded) { Query *parse = root->parse; int result_relation = parse->resultRelation; @@ -134,6 +138,15 @@ preprocess_targetlist(PlannerInfo *root) if (rc->rti != rc->prti) continue; + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which will change the parent row + * mark's allMarkTypes fields. + */ + if (rc->isParent && !inheritance_expanded) + continue; + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) { /* Need to fetch TID */ diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 134e7bafd9..e4d4e7d6dd 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -39,15 +39,19 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" +#include "optimizer/var.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" +#include "partitioning/partprune.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/selfuncs.h" @@ -99,23 +103,26 @@ static List *generate_append_tlist(List *colTypes, List *colCollations, List *input_tlists, List *refnames_tlist); static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist); +static void expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti); static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); + Index rti, RelOptInfo *rel); static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); -static void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p); +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); +static RelOptInfo *build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, @@ -1472,33 +1479,134 @@ generate_setop_grouplist(SetOperationStmt *op, List *targetlist) void expand_inherited_tables(PlannerInfo *root) { - Index nrtes; + int orig_rtable_size; Index rti; - ListCell *rl; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; /* - * expand_inherited_rtentry may add RTEs to parse->rtable. The function is + * expand_append_rtentry may add RTEs to parse->rtable. The function is * expected to recursively handle any RTEs that it creates with inh=true. * So just scan as far as the original end of the rtable list. */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) + for (rti = 1; rti < orig_rtable_size; rti++) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + if (rte->inh) + expand_append_rtentry(root, brel, rte, rti); + } +} + +/* + * expand_append_rtentry + * This initializes RelOptInfos for an appendrel's child relations, if + * any + * + * 'rel' is the appendrel parent, whose range table entry ('rte') has been + * marked to require adding children. An appendrel parent could either + * be a subquery (if we flattened UNION ALL query) or a table that's known + * to have inheritance children. The latter consists of both regular + * inheritance parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as an appendrel child. The original RTE is + * considered to represent the whole inheritance set. + */ +static void +expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, + Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_append_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_inherited_rtentry(root, rte, rti, rel); } } /* * expand_inherited_rtentry - * Check whether a rangetable entry represents an inheritance set. - * If so, add entries for all the child tables to the query's - * rangetable, and build AppendRelInfo nodes for all the child tables - * and add them to root->append_rel_list. If not, clear the entry's - * "inh" flag to prevent later code from looking for AppendRelInfos. + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. * * Note that the original RTE is considered to represent the whole * inheritance set. The first of the generated RTEs is an RTE for the same @@ -1509,45 +1617,22 @@ expand_inherited_tables(PlannerInfo *root) * regular inheritance, a parent RTE must always have at least two associated * AppendRelInfos: one corresponding to the parent table as a simple member of * inheritance set and one or more corresponding to the actual children. - * Since a partitioned table is not scanned, it might have only one associated - * AppendRelInfo. */ static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, + RelOptInfo *rel) { Oid parentOID; PlanRowMark *oldrc; - Relation oldrelation; - LOCKMODE lockmode; + LOCKMODE lockmode = rte->rellockmode; List *inhOIDs; ListCell *l; + int num_children; + int num_children_added = 0; - /* Does RT entry allow inheritance? */ - if (!rte->inh) - return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } - /* Fast path for common case of childless table */ + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); parentOID = rte->relid; - if (!has_subclass(parentOID)) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * The rewriter should already have obtained an appropriate lock on each - * relation named in the query. However, for each child relation we add - * to the query, we must obtain an appropriate lock, because this will be - * the first use of those relations in the parse/rewrite/plan pipeline. - * Child rels should use the same lockmode as their parent. - */ - lockmode = rte->rellockmode; /* Scan for all members of inheritance set, acquire needed locks */ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); @@ -1557,7 +1642,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) * case. This could happen despite above has_subclass() check, if table * once had a child but no longer does. */ - if (list_length(inhOIDs) < 2) + num_children = list_length(inhOIDs); + if (num_children < 2) { /* Clear flag before returning */ rte->inh = false; @@ -1565,216 +1651,220 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } /* - * If parent relation is selected FOR UPDATE/SHARE, we need to mark its - * PlanRowMark as isParent = true, and generate a new PlanRowMark for each - * child. + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. */ oldrc = get_plan_rowmark(root->rowMarks, rti); - if (oldrc) - oldrc->isParent = true; + Assert(oldrc == NULL || oldrc->isParent); /* - * Must open the parent relation to examine its tupdesc. We need not lock - * it; we assume the rewriter already did. + * Must expand PlannerInfo arrays by num_children before we can add + * children. */ - oldrelation = heap_open(parentOID, NoLock); + expand_planner_arrays(root, num_children); - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) + foreach(l, inhOIDs) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); - - /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. - */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); - } - else - { - List *appinfos = NIL; + Oid childOID = lfirst_oid(l); + Relation newrelation; RangeTblEntry *childrte; Index childRTindex; + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. */ - foreach(l, inhOIDs) + if (RELATION_IS_OTHER_TEMP(newrelation)) { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = heap_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - heap_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - heap_close(newrelation, NoLock); + heap_close(newrelation, lockmode); + continue; } - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); + (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation, &childrte, + &childRTindex); + Assert(childrte != NULL); + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + Assert(childRTindex > 0); + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; } - heap_close(oldrelation, NoLock); + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } } /* * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * in order to avoid partitions being locked in a different order than other + * places in the backend that may lock partitions. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) + Index parentRTindex, RelOptInfo *parentrel) { + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; - check_stack_depth(); - - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + expand_planner_arrays(root, bms_num_members(partindexes)); /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. */ - if (partdesc->nparts == 0) - { - parentrte->inh = false; - return; - } + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); - for (i = 0; i < partdesc->nparts; i++) + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) { - Oid childOID = partdesc->oids[i]; - Relation childrel; + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + RangeTblEntry *childrte; + Index childRTindex; - /* Open rel; we already have required locks */ - childrel = heap_open(childOID, NoLock); + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation, + &childrte, &childRTindex); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + + /* If the child is partitioned itself, expand it too. */ + if (childrel->part_scheme) + { + Assert(childrte->inh); expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); + childrel); + } + } - /* Close child relation, but keep locks */ - heap_close(childrel, NoLock); + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); } } /* - * expand_single_inheritance_child - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) + * The return value is the RelOptInfo that's added. * * PlanRowMarks still carry the top-parent's RTI, and the top-parent's * allMarkTypes field still accumulates values from all descendents. * * "parentrte" and "parentRTindex" are immediate parent's RTE and * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. */ -static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); Oid childOID = RelationGetRelid(childrel); RangeTblEntry *childrte; Index childRTindex; AppendRelInfo *appinfo; + RelOptInfo *childrelopt; /* * Build an RTE for the child, and attach to query's rangetable list. We * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh = false. Also, set requiredPerms to zero since - * all required permissions checks are done on the original RTE. Likewise, - * set the child's securityQuals to empty, because we only want to apply - * the parent's RLS conditions regardless of what RLS properties + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties * individual children may have. (This is an intentional choice to make * inherited RLS work like regular permissions checks.) The parent * securityQuals will be propagated to children along with other base @@ -1784,55 +1874,42 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childrte_p = childrte; childrte->relid = childOID; childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) - childrte->inh = true; - else - childrte->inh = false; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); childrte->requiredPerms = 0; childrte->securityQuals = NIL; parse->rtable = lappend(parse->rtable, childrte); childRTindex = list_length(parse->rtable); *childRTindex_p = childRTindex; - /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. - */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) - { - appinfo = makeNode(AppendRelInfo); - appinfo->parent_relid = parentRTindex; - appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->rd_rel->reltype; - appinfo->child_reltype = childrel->rd_rel->reltype; - make_inh_translation_list(parentrel, childrel, childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = parentOID; - *appinfos = lappend(*appinfos, appinfo); + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + childrel->rd_att, + childOID, + childrel->rd_rel->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); } /* @@ -1864,6 +1941,24 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need need to create + * one, because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_append_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; } /* @@ -1874,14 +1969,11 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, * For paranoia's sake, we match type/collation as well as attribute name. */ static void -make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, - List **translated_vars) +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) { List *vars = NIL; - TupleDesc old_tupdesc = RelationGetDescr(oldrelation); - TupleDesc new_tupdesc = RelationGetDescr(newrelation); - Oid new_relid = RelationGetRelid(newrelation); int oldnatts = old_tupdesc->natts; int newnatts = new_tupdesc->natts; int old_attno; @@ -1911,7 +2003,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, * When we are generating the "translation list" for the parent table * of an inheritance set, no need to search for matches. */ - if (oldrelation == newrelation) + if (from_rel == to_rel) { vars = lappend(vars, makeVar(newvarno, (AttrNumber) (old_attno + 1), @@ -1937,10 +2029,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, { HeapTuple newtup; - newtup = SearchSysCacheAttName(new_relid, attname); + newtup = SearchSysCacheAttName(to_rel, attname); if (!newtup) elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; ReleaseSysCache(newtup); @@ -1950,10 +2042,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, /* Found it, check type and collation match */ if (atttypid != att->atttypid || atttypmod != att->atttypmod) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); if (attcollation != att->attcollation) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); vars = lappend(vars, makeVar(newvarno, (AttrNumber) (new_attno + 1), @@ -2020,6 +2112,255 @@ translate_col_privs(const Bitmapset *parent_privs, } /* + * build_append_child_rel + * Build a RelOptInfo for child relation of an append rel + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields base on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turns out to be self-contradictory, the + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + List *childquals; + ListCell *lc; + bool have_const_false_cq; + Index cq_min_security; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = false; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, parent->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual = (Node *) rinfo->clause; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, childqual, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might + * have securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals.) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except + * that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* + * not likely that we'd see constants here, so no + * check + */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, + false, + false, + security_level, + NULL, NULL, + NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* Set child's version of baserestrictinfo. */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; +} + +AppendRelInfo * +make_append_rel_info(RelOptInfo *parentrel, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex) +{ + AppendRelInfo *appinfo = makeNode(AppendRelInfo); + + appinfo->parent_relid = parentrel->relid; + appinfo->child_relid = childRTindex; + appinfo->parent_reltype = parentrel->reltype; + appinfo->child_reltype = childreltype; + make_inh_translation_list(parentrel->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, + &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; + + return appinfo; +} + +/* * adjust_appendrel_attrs * Copy the specified query or expression and translate Vars referring to a * parent rel to refer to the corresponding child rel instead. We also diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile index c54d0a690d..da9ccf32b4 100644 --- a/src/backend/optimizer/util/Makefile +++ b/src/backend/optimizer/util/Makefile @@ -13,6 +13,6 @@ top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global OBJS = clauses.o joininfo.o orclauses.o pathnode.o placeholder.o \ - plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o + plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index a0fed5be42..bf441741e0 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -106,7 +106,7 @@ static void set_baserel_partition_key_exprs(Relation relation, */ void get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) + Bitmapset *updatedCols, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; @@ -444,11 +444,31 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, get_relation_foreign_keys(root, rel, relation, inhparent); /* - * Collect info about relation's partitioning scheme, if any. Only - * inheritance parents may be partitioned. + * Collect some additional information for inheritance parents. */ - if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - set_relation_partition_info(root, rel, relation); + if (inhparent) + { + /* + * We'll need the TupleDesc when initializing the child relation. + * A copy is being made because concurrent changes might drop + * the relcache entry. That's possible because ALTER TABLE + * child_table NO INHERIT parent_table only requires an + * AccessShareLock on parent_table. + */ + rel->tupdesc = CreateTupleDescCopy(RelationGetDescr(relation)); + rel->reltype = RelationGetForm(relation)->reltype; + + /* + * If partitioned, also save the information of partitioning scheme, + * and whether the query updates any of the partition key columns. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + set_relation_partition_info(root, rel, relation); + root->partColsUpdated |= has_partition_attrs(relation, updatedCols, + NULL); + } + } heap_close(relation, NoLock); @@ -1853,16 +1873,20 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; + + /* + * Since we must've taken a lock on the table, it's okay to simply copy + * the pointers to relcache data here. + */ + rel->part_oids = partdesc->oids; + rel->boundinfo = partdesc->boundinfo; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 29ba19349f..c23db9d78d 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -130,6 +130,37 @@ setup_append_rel_array(PlannerInfo *root) } } +/* Expand the PlannerInfo arrays by add_size members and zero-init it. */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size = root->simple_rel_array_size + add_size; + + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + if (root->append_rel_array) + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + else + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + + /* Set the contents of just allocated memory to 0. */ + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + root->simple_rel_array_size = new_size; +} + /* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. @@ -238,7 +269,8 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte->relid, rte->inh, rte->updatedCols, + rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -279,52 +311,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); - /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. - */ - if (rte->inh) - { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); - - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; - } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); - } - return rel; } diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 35c87535d3..fcf8d6032c 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -45,6 +45,7 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/planner.h" #include "optimizer/predtest.h" @@ -436,17 +437,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -540,23 +547,20 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ @@ -564,6 +568,13 @@ prune_append_rel_partitions(RelOptInfo *rel) return NULL; /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. */ @@ -590,15 +601,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 94f14019bd..e6fdbcd030 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -15,6 +15,7 @@ #define RELATION_H #include "access/sdir.h" +#include "access/tupdesc.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/params.h" @@ -723,6 +724,7 @@ typedef struct RelOptInfo int nparts; /* number of partitions */ struct PartitionBoundInfoData *boundinfo; /* Partition bounds */ List *partition_qual; /* partition constraint */ + Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ List **partexprs; /* Non-nullable partition key expressions. */ @@ -732,6 +734,12 @@ typedef struct RelOptInfo Index inh_root_parent; /* For otherrels, this is the RT index of * inheritance table mentioned in the query * from which this relation originated */ + /* + * Set only if this is an inheritance parent relation. This information + * is needed when initializing the planning info for children. + */ + TupleDesc tupdesc; /* A "copy" of the table's tuple desriptor */ + Oid reltype; /* Table's reltype */ } RelOptInfo; /* diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 81abcf53a8..b1baa3117a 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -261,6 +261,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index 7d53cbbb87..edaf2a3b4f 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -26,7 +26,7 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); + bool inhparent, Bitmapset *updatedCols, RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 38608770a2..faae07d240 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -38,7 +38,8 @@ extern Expr *canonicalize_qual(Expr *qual, bool is_check); /* * prototypes for preptlist.c */ -extern List *preprocess_targetlist(PlannerInfo *root); +extern List *preprocess_targetlist(PlannerInfo *root, + bool inheritance_expanded); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); @@ -51,7 +52,10 @@ extern void expand_inherited_tables(PlannerInfo *root); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); - +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parentrel, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex); extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, Relids child_relids, Relids top_parent_relids); @@ -64,5 +68,4 @@ extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, Relids left_relids, Relids right_relids); extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, Relids child_relids, Relids top_parent_relids); - #endif /* PREP_H */ diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h index e07aaaf798..ac0a979010 100644 --- a/src/include/partitioning/partprune.h +++ b/src/include/partitioning/partprune.h @@ -76,7 +76,7 @@ extern PartitionPruneInfo *make_partition_pruneinfo(PlannerInfo *root, List *subpaths, List *partitioned_rels, List *prunequal); -extern Relids prune_append_rel_partitions(RelOptInfo *rel); +extern Bitmapset *prune_append_rel_partitions(RelOptInfo *rel); extern Bitmapset *get_matching_partitions(PartitionPruneContext *context, List *pruning_steps); diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 6bc106831e..1450cef057 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) -- 2.11.0
v8-0004-Move-append-expansion-code-into-its-own-file.patch (text/plain, 112.3K) download From f07f25e9c7057d8240b39573a83f9410633dba83 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 7 Nov 2018 16:51:31 +0900 Subject: [PATCH v8 4/6] Move append expansion code into its own file This commit moves expand_append_rel and underlings currently in optimizer/prep/prepunionc.c to optimizer/utils/append.c. All of the AppendRelInfo based expression manipulation routines are moved to optimizer/utils/appendinfo.c. This commit only moves the code and contains no functional changes. --- src/backend/optimizer/path/allpaths.c | 2 + src/backend/optimizer/path/equivclass.c | 1 + src/backend/optimizer/path/joinrels.c | 1 + src/backend/optimizer/plan/planner.c | 1 + src/backend/optimizer/prep/prepunion.c | 1574 ------------------------------- src/backend/optimizer/util/Makefile | 5 +- src/backend/optimizer/util/append.c | 777 +++++++++++++++ src/backend/optimizer/util/appendinfo.c | 851 +++++++++++++++++ src/backend/optimizer/util/pathnode.c | 1 + src/backend/optimizer/util/relnode.c | 1 + src/backend/partitioning/partprune.c | 1 + src/include/optimizer/append.h | 25 + src/include/optimizer/appendinfo.h | 43 + src/include/optimizer/prep.h | 19 - 14 files changed, 1707 insertions(+), 1595 deletions(-) create mode 100644 src/backend/optimizer/util/append.c create mode 100644 src/backend/optimizer/util/appendinfo.c create mode 100644 src/include/optimizer/append.h create mode 100644 src/include/optimizer/appendinfo.h diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index d44d034f24..4261e79bc6 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -30,6 +30,8 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "optimizer/append.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/geqo.h" diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 029665b974..f52adc72d0 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -22,6 +22,7 @@ #include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index da0831de4e..6e321ec9e7 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -15,6 +15,7 @@ #include "postgres.h" #include "miscadmin.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 7f5a1865ba..0586c48ed0 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -39,6 +39,7 @@ #include "nodes/print.h" #endif #include "nodes/relation.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index e4d4e7d6dd..70f37c593f 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -12,11 +12,6 @@ * case, but most of the heavy lifting for that is done elsewhere, * notably in prepjointree.c and allpaths.c. * - * There is also some code here to support planning of queries that use - * inheritance (SELECT FROM foo*). Inheritance trees are converted into - * append relations, and thenceforth share code with the UNION ALL case. - * - * * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -39,32 +34,21 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" -#include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" -#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" -#include "optimizer/var.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/selfuncs.h" #include "utils/syscache.h" -typedef struct -{ - PlannerInfo *root; - int nappinfos; - AppendRelInfo **appinfos; -} adjust_appendrel_attrs_context; - static RelOptInfo *recurse_set_operations(Node *setOp, PlannerInfo *root, List *colTypes, List *colCollations, bool junkOK, @@ -103,34 +87,6 @@ static List *generate_append_tlist(List *colTypes, List *colCollations, List *input_tlists, List *refnames_tlist); static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist); -static void expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, - RangeTblEntry *rte, Index rti); -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti, RelOptInfo *rel); -static void expand_partitioned_rtentry(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel); -static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel, - PlanRowMark *top_parentrc, Relation childrel, - RangeTblEntry **childrte_p, Index *childRTindex_p); -static void make_inh_translation_list(TupleDesc old_tupdesc, - TupleDesc new_tupdesc, - Oid from_rel, Oid to_rel, - Index newvarno, List **translated_vars); -static RelOptInfo *build_append_child_rel(PlannerInfo *root, - RelOptInfo *parent, - Index childRTindex); -static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); -static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars); -static Node *adjust_appendrel_attrs_mutator(Node *node, - adjust_appendrel_attrs_context *context); -static Relids adjust_child_relids(Relids relids, int nappinfos, - AppendRelInfo **appinfos); -static List *adjust_inherited_tlist(List *tlist, - AppendRelInfo *context); /* @@ -1467,1533 +1423,3 @@ generate_setop_grouplist(SetOperationStmt *op, List *targetlist) Assert(lg == NULL); return grouplist; } - - -/* - * expand_inherited_tables - * Expand each rangetable entry that represents an inheritance set - * into an "append relation". At the conclusion of this process, - * the "inh" flag is set in all and only those RTEs that are append - * relation parents. - */ -void -expand_inherited_tables(PlannerInfo *root) -{ - int orig_rtable_size; - Index rti; - - Assert(root->simple_rel_array_size > 0); - orig_rtable_size = root->simple_rel_array_size; - - /* - * expand_append_rtentry may add RTEs to parse->rtable. The function is - * expected to recursively handle any RTEs that it creates with inh=true. - * So just scan as far as the original end of the rtable list. - */ - for (rti = 1; rti < orig_rtable_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - RangeTblEntry *rte = root->simple_rte_array[rti]; - - /* there may be empty slots corresponding to non-baserel RTEs */ - if (brel == NULL) - continue; - - if (rte->inh) - expand_append_rtentry(root, brel, rte, rti); - } -} - -/* - * expand_append_rtentry - * This initializes RelOptInfos for an appendrel's child relations, if - * any - * - * 'rel' is the appendrel parent, whose range table entry ('rte') has been - * marked to require adding children. An appendrel parent could either - * be a subquery (if we flattened UNION ALL query) or a table that's known - * to have inheritance children. The latter consists of both regular - * inheritance parents and partitioned tables. - * - * For a subquery parent, there is not much to be done here because the - * children's RTEs are already present in the query, so we just initialize - * RelOptInfos for them. Also, the AppendRelInfos for child subqueries - * have already been added. - * - * For tables, we need to add the children to the range table and initialize - * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For - * a partitioned parent, we only add the children remaining after pruning. - * For regular inheritance parents, we find the children using - * find_all_inheritors and add all of them. - * - * If it turns out that there are no children, then we set rte->inh to false - * to let the caller know that only the parent table needs to be scanned. The - * caller can accordingly switch to a non-Append path. For a partitioned - * parent, that means an empty relation because parents themselves contain no - * data. - * - * For the regular inheritance case, the parent also gets another RTE with - * inh = false to represent it as an appendrel child. The original RTE is - * considered to represent the whole inheritance set. - */ -static void -expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, - Index rti) -{ - Assert(rte->inh); - /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ - Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); - - /* - * UNION ALL children already got RTEs and AppendRelInfos, so just build - * RelOptInfos and return. - * - * It might be a bit odd that this code is in this, because there is - * nothing to expand really. - */ - if (rte->rtekind == RTE_SUBQUERY) - { - ListCell *l; - - /* - * We don't need to use expand_planner_arrays in this case, because - * no new child RTEs are created. setup_simple_rel_arrays() and - * setup_append_rel_array would've considered these child RTEs when - * allocating space for various arrays. - */ - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst(l); - Index childRTindex = appinfo->child_relid; - - if (appinfo->parent_relid != rti) - continue; - - Assert(childRTindex < root->simple_rel_array_size); - Assert(root->simple_rte_array[childRTindex] != NULL); - - /* - * We set the correct value of baserestricinfo and - * baserestrict_min_security below. - */ - root->simple_rel_array[childRTindex] = - build_append_child_rel(root, rel, appinfo->child_relid); - } - } - else - { - Assert(rte->rtekind == RTE_RELATION); - Assert(has_subclass(rte->relid)); - - /* - * The rewriter should already have obtained an appropriate lock on - * each relation named in the query. However, for each child relation - * we add to the query, we must obtain an appropriate lock, because - * this will be the first use of those relations in the - * parse/rewrite/plan pipeline. Child rels should use the same - * lockmode as their parent. - */ - Assert(rte->rellockmode != NoLock); - - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - expand_partitioned_rtentry(root, rte, rti, rel); - else - expand_inherited_rtentry(root, rte, rti, rel); - } -} - -/* - * expand_inherited_rtentry - * Add entries for all the child tables to the query's rangetable, and - * build AppendRelInfo nodes for all the child tables and add them to - * root->append_rel_list. - * - * Note that the original RTE is considered to represent the whole - * inheritance set. The first of the generated RTEs is an RTE for the same - * table, but with inh = false, to represent the parent table in its role - * as a simple member of the inheritance set. - * - * A childless table is never considered to be an inheritance set. For - * regular inheritance, a parent RTE must always have at least two associated - * AppendRelInfos: one corresponding to the parent table as a simple member of - * inheritance set and one or more corresponding to the actual children. - */ -static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, - RelOptInfo *rel) -{ - Oid parentOID; - PlanRowMark *oldrc; - LOCKMODE lockmode = rte->rellockmode; - List *inhOIDs; - ListCell *l; - int num_children; - int num_children_added = 0; - - Assert(rte->rtekind == RTE_RELATION); - Assert(lockmode != NoLock); - parentOID = rte->relid; - - /* Scan for all members of inheritance set, acquire needed locks */ - inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); - - /* - * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite above has_subclass() check, if table - * once had a child but no longer does. - */ - num_children = list_length(inhOIDs); - if (num_children < 2) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks - * should've set isParent = true. We'll generate a new PlanRowMark for - * each child. - */ - oldrc = get_plan_rowmark(root->rowMarks, rti); - Assert(oldrc == NULL || oldrc->isParent); - - /* - * Must expand PlannerInfo arrays by num_children before we can add - * children. - */ - expand_planner_arrays(root, num_children); - - foreach(l, inhOIDs) - { - Oid childOID = lfirst_oid(l); - Relation newrelation; - RangeTblEntry *childrte; - Index childRTindex; - - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (RELATION_IS_OTHER_TEMP(newrelation)) - { - heap_close(newrelation, lockmode); - continue; - } - - (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, - newrelation, &childrte, - &childRTindex); - Assert(childrte != NULL); - /* All regular inheritance children are leaf children. */ - Assert(!childrte->inh); - Assert(childRTindex > 0); - - /* Close child relations, but keep locks */ - heap_close(newrelation, NoLock); - num_children_added++; - } - - /* - * If all children, including the parent (as child rel), were - * excluded, mark the parent rel as empty. If all the children were temp - * tables, pretend it's a non-inheritance situation; we don't need Append - * node in that case. The duplicate RTE we added for the parent table is - * harmless, so we don't bother to get rid of it; ditto for the useless - * PlanRowMark node. - */ - if (num_children_added == 0) - mark_dummy_rel(rel); - else if (num_children_added == 1) - rte->inh = false; - - /* - * Add junk columns needed by the row mark if any and also add the - * relevant expressions to the root parent's reltarget. - */ - if (oldrc) - { - List *tlist = add_rowmark_junk_columns(root, oldrc); - - build_base_rel_tlists(root, tlist); - } -} - -/* - * expand_partitioned_rtentry - * Prunes unnecessary partitions of a partitioned table and adds - * remaining ones to the Query and the PlannerInfo - * - * Partitions are added to the query in order in which they are found in - * the parent's PartitionDesc. - * - * Note: even though only the unpruned partitions will be added to the - * resulting plan, this still locks *all* partitions via find_all_inheritors - * in order to avoid partitions being locked in a different order than other - * places in the backend that may lock partitions. - */ -static void -expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel) -{ - LOCKMODE lockmode = parentrte->rellockmode; - PlanRowMark *rootrc = NULL; - int i; - Bitmapset *partindexes; - Index rootParentRTindex = parentrel->inh_root_parent > 0 ? - parentrel->inh_root_parent : - parentRTindex; - - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - - /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, values of the - * indexes of partitioned relations that appear down in the tree will be - * bubbled up into root parent's list so that when we've created Paths for - * all the children, the root table's list will contain all such indexes. - */ - parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - - /* Perform pruning. */ - partindexes = prune_append_rel_partitions(parentrel); - - /* Must expand PlannerInfo arrays before we can add children. */ - expand_planner_arrays(root, bms_num_members(partindexes)); - - /* - * For partitioned tables, we also store the partition RelOptInfo - * pointers in the parent's RelOptInfo. - */ - parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * - parentrel->nparts); - - rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); - Assert(rootrc == NULL || rootrc->isParent); - i = -1; - while ((i = bms_next_member(partindexes, i)) >= 0) - { - Oid childOID = parentrel->part_oids[i]; - Relation newrelation; - RelOptInfo *childrel; - RangeTblEntry *childrte; - Index childRTindex; - - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); - Assert(!RELATION_IS_OTHER_TEMP(newrelation)); - - /* - * A partitioned child table with 0 children is a dummy rel, so don't - * bother creating planner objects for it. - */ - if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && - RelationGetPartitionDesc(newrelation)->nparts == 0) - { - heap_close(newrelation, NoLock); - continue; - } - - childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, - parentrel, rootrc, newrelation, - &childrte, &childRTindex); - Assert(childrel != NULL); - parentrel->part_rels[i] = childrel; - - /* Close child relations, but keep locks */ - heap_close(newrelation, NoLock); - - /* If the child is partitioned itself, expand it too. */ - if (childrel->part_scheme) - { - Assert(childrte->inh); - expand_partitioned_rtentry(root, childrte, childRTindex, - childrel); - } - } - - /* - * Add junk columns needed by the row mark if any and also add the - * relevant expressions to the root parent's reltarget. - */ - if (rootrc) - { - List *tlist = add_rowmark_junk_columns(root, rootrc); - - build_base_rel_tlists(root, tlist); - } -} - -/* - * add_inheritance_child_rel - * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally - * a RelOptInfo for an inheritance child relation. - * - * The return value is the RelOptInfo that's added. - * - * PlanRowMarks still carry the top-parent's RTI, and the top-parent's - * allMarkTypes field still accumulates values from all descendents. - * - * "parentrte" and "parentRTindex" are immediate parent's RTE and - * RTI. "top_parentrc" is top parent's PlanRowMark. - */ -static RelOptInfo * -add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel, - PlanRowMark *top_parentrc, Relation childrel, - RangeTblEntry **childrte_p, Index *childRTindex_p) -{ - Query *parse = root->parse; - Oid childOID = RelationGetRelid(childrel); - RangeTblEntry *childrte; - Index childRTindex; - AppendRelInfo *appinfo; - RelOptInfo *childrelopt; - - /* - * Build an RTE for the child, and attach to query's rangetable list. We - * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh appropriately. Also, set requiredPerms to zero - * since all required permissions checks are done on the original RTE. - * Likewise, set the child's securityQuals to empty, because we only want - * to apply the parent's RLS conditions regardless of what RLS properties - * individual children may have. (This is an intentional choice to make - * inherited RLS work like regular permissions checks.) The parent - * securityQuals will be propagated to children along with other base - * restriction clauses, so we don't need to do it here. - */ - childrte = copyObject(parentrte); - *childrte_p = childrte; - childrte->relid = childOID; - childrte->relkind = childrel->rd_rel->relkind; - /* - * A partitioned child will need to be expanded as an append parent - * itself, so set its inh to true. - */ - childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); - childrte->requiredPerms = 0; - childrte->securityQuals = NIL; - parse->rtable = lappend(parse->rtable, childrte); - childRTindex = list_length(parse->rtable); - *childRTindex_p = childRTindex; - - /* Create an AppendRelInfo and add it to planner's global list. */ - appinfo = make_append_rel_info(parentrel, parentrte, - childrel->rd_att, - childOID, - childrel->rd_rel->reltype, - childRTindex); - root->append_rel_list = lappend(root->append_rel_list, appinfo); - - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childrte->relid != parentrte->relid) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } - - /* - * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE. - */ - if (top_parentrc) - { - PlanRowMark *childrc = makeNode(PlanRowMark); - - childrc->rti = childRTindex; - childrc->prti = top_parentrc->rti; - childrc->rowmarkId = top_parentrc->rowmarkId; - /* Reselect rowmark type, because relkind might not match parent */ - childrc->markType = select_rowmark_type(childrte, - top_parentrc->strength); - childrc->allMarkTypes = (1 << childrc->markType); - childrc->strength = top_parentrc->strength; - childrc->waitPolicy = top_parentrc->waitPolicy; - - /* - * We mark RowMarks for partitioned child tables as parent RowMarks so - * that the executor ignores them (except their existence means that - * the child tables be locked using appropriate mode). - */ - childrc->isParent = (childrte->relkind == RELKIND_PARTITIONED_TABLE); - - /* Include child's rowmark type in top parent's allMarkTypes */ - top_parentrc->allMarkTypes |= childrc->allMarkTypes; - - root->rowMarks = lappend(root->rowMarks, childrc); - } - - /* - * Add the RelOptInfo. Even though we may not really scan this relation - * for reasons such as contradictory quals, we still need need to create - * one, because for every RTE in the query's range table, there must be an - * accompanying RelOptInfo. - */ - - /* First, store the RTE and appinfos into planner arrays. */ - Assert(root->simple_rte_array[childRTindex] == NULL); - root->simple_rte_array[childRTindex] = childrte; - Assert(root->append_rel_array[childRTindex] == NULL); - root->append_rel_array[childRTindex] = appinfo; - - childrelopt = build_append_child_rel(root, parentrel, childRTindex); - Assert(childrelopt != NULL); - - return childrelopt; -} - -/* - * make_inh_translation_list - * Build the list of translations from parent Vars to child Vars for - * an inheritance child. - * - * For paranoia's sake, we match type/collation as well as attribute name. - */ -static void -make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, - Oid from_rel, Oid to_rel, - Index newvarno, List **translated_vars) -{ - List *vars = NIL; - int oldnatts = old_tupdesc->natts; - int newnatts = new_tupdesc->natts; - int old_attno; - int new_attno = 0; - - for (old_attno = 0; old_attno < oldnatts; old_attno++) - { - Form_pg_attribute att; - char *attname; - Oid atttypid; - int32 atttypmod; - Oid attcollation; - - att = TupleDescAttr(old_tupdesc, old_attno); - if (att->attisdropped) - { - /* Just put NULL into this list entry */ - vars = lappend(vars, NULL); - continue; - } - attname = NameStr(att->attname); - atttypid = att->atttypid; - atttypmod = att->atttypmod; - attcollation = att->attcollation; - - /* - * When we are generating the "translation list" for the parent table - * of an inheritance set, no need to search for matches. - */ - if (from_rel == to_rel) - { - vars = lappend(vars, makeVar(newvarno, - (AttrNumber) (old_attno + 1), - atttypid, - atttypmod, - attcollation, - 0)); - continue; - } - - /* - * Otherwise we have to search for the matching column by name. - * There's no guarantee it'll have the same column position, because - * of cases like ALTER TABLE ADD COLUMN and multiple inheritance. - * However, in simple cases, the relative order of columns is mostly - * the same in both relations, so try the column of newrelation that - * follows immediately after the one that we just found, and if that - * fails, let syscache handle it. - */ - if (new_attno >= newnatts || - (att = TupleDescAttr(new_tupdesc, new_attno))->attisdropped || - strcmp(attname, NameStr(att->attname)) != 0) - { - HeapTuple newtup; - - newtup = SearchSysCacheAttName(to_rel, attname); - if (!newtup) - elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, get_rel_name(to_rel)); - new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; - ReleaseSysCache(newtup); - - att = TupleDescAttr(new_tupdesc, new_attno); - } - - /* Found it, check type and collation match */ - if (atttypid != att->atttypid || atttypmod != att->atttypmod) - elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, get_rel_name(to_rel)); - if (attcollation != att->attcollation) - elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, get_rel_name(to_rel)); - - vars = lappend(vars, makeVar(newvarno, - (AttrNumber) (new_attno + 1), - atttypid, - atttypmod, - attcollation, - 0)); - new_attno++; - } - - *translated_vars = vars; -} - -/* - * translate_col_privs - * Translate a bitmapset representing per-column privileges from the - * parent rel's attribute numbering to the child's. - * - * The only surprise here is that we don't translate a parent whole-row - * reference into a child whole-row reference. That would mean requiring - * permissions on all child columns, which is overly strict, since the - * query is really only going to reference the inherited columns. Instead - * we set the per-column bits for all inherited columns. - */ -static Bitmapset * -translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars) -{ - Bitmapset *child_privs = NULL; - bool whole_row; - int attno; - ListCell *lc; - - /* System attributes have the same numbers in all tables */ - for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) - { - if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - attno - FirstLowInvalidHeapAttributeNumber); - } - - /* Check if parent has whole-row reference */ - whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, - parent_privs); - - /* And now translate the regular user attributes, using the vars list */ - attno = InvalidAttrNumber; - foreach(lc, translated_vars) - { - Var *var = lfirst_node(Var, lc); - - attno++; - if (var == NULL) /* ignore dropped columns */ - continue; - if (whole_row || - bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - var->varattno - FirstLowInvalidHeapAttributeNumber); - } - - return child_privs; -} - -/* - * build_append_child_rel - * Build a RelOptInfo for child relation of an append rel - * - * After creating the RelOptInfo for the given child RT index, it goes on to - * initialize some of its fields base on the parent RelOptInfo. - * - * If the quals in baserestrictinfo turns out to be self-contradictory, the - * RelOptInfo is marked dummy before returning. - */ -static RelOptInfo * -build_append_child_rel(PlannerInfo *root, - RelOptInfo *parent, - Index childRTindex) -{ - RelOptInfo *childrel; - RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; - AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; - List *childquals; - ListCell *lc; - bool have_const_false_cq; - Index cq_min_security; - - /* Build the RelOptInfo. */ - childrel = build_simple_rel(root, childRTindex, parent); - - /* - * Propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - */ - childrel->direct_lateral_relids = parent->direct_lateral_relids; - childrel->lateral_relids = parent->lateral_relids; - childrel->lateral_referencers = parent->lateral_referencers; - - /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) - */ - childquals = false; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, parent->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual = (Node *) rinfo->clause; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, childqual, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might - * have securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals.) Pull any such securityQuals up into the - * baserestrictinfo for the child. This is similar to - * process_security_barrier_quals() for the parent rel, except - * that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* - * not likely that we'd see constants here, so no - * check - */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, - false, - false, - security_level, - NULL, NULL, - NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* Set child's version of baserestrictinfo. */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - } - - return childrel; -} - -/* - * add_rowmark_junk_columns - * Add necessary junk columns for rowmarked inheritance parent rel. - * - * These values are needed for locking of rels selected FOR UPDATE/SHARE, and - * to do EvalPlanQual rechecking. See comments for PlanRowMark in - * plannodes.h. - */ -static List * -add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) -{ - List *tlist = root->processed_tlist; - Var *var; - char resname[32]; - TargetEntry *tle; - - if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) - { - /* Need to fetch TID */ - var = makeVar(rc->rti, - SelfItemPointerAttributeNumber, - TIDOID, - -1, - InvalidOid, - 0); - snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - } - if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) - { - /* Need the whole row as a junk var */ - var = makeWholeRowVar(root->simple_rte_array[rc->rti], - rc->rti, - 0, - false); - snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - } - - /* For inheritance cases, always fetch the tableoid too. */ - var = makeVar(rc->rti, - TableOidAttributeNumber, - OIDOID, - -1, - InvalidOid, - 0); - snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - - return tlist; -} - -AppendRelInfo * -make_append_rel_info(RelOptInfo *parentrel, RangeTblEntry *parentrte, - TupleDesc childdesc, Oid childoid, Oid childreltype, - Index childRTindex) -{ - AppendRelInfo *appinfo = makeNode(AppendRelInfo); - - appinfo->parent_relid = parentrel->relid; - appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->reltype; - appinfo->child_reltype = childreltype; - make_inh_translation_list(parentrel->tupdesc, childdesc, - parentrte->relid, childoid, - childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = parentrte->relid; - - return appinfo; -} - -/* - * adjust_appendrel_attrs - * Copy the specified query or expression and translate Vars referring to a - * parent rel to refer to the corresponding child rel instead. We also - * update rtindexes appearing outside Vars, such as resultRelation and - * jointree relids. - * - * Note: this is only applied after conversion of sublinks to subplans, - * so we don't need to cope with recursion into sub-queries. - * - * Note: this is not hugely different from what pullup_replace_vars() does; - * maybe we should try to fold the two routines together. - */ -Node * -adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, - AppendRelInfo **appinfos) -{ - Node *result; - adjust_appendrel_attrs_context context; - - context.root = root; - context.nappinfos = nappinfos; - context.appinfos = appinfos; - - /* If there's nothing to adjust, don't call this function. */ - Assert(nappinfos >= 1 && appinfos != NULL); - - /* - * Must be prepared to start with a Query or a bare expression tree. - */ - if (node && IsA(node, Query)) - { - Query *newnode; - int cnt; - - newnode = query_tree_mutator((Query *) node, - adjust_appendrel_attrs_mutator, - (void *) &context, - QTW_IGNORE_RC_SUBQUERIES); - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (newnode->resultRelation == appinfo->parent_relid) - { - newnode->resultRelation = appinfo->child_relid; - /* Fix tlist resnos too, if it's inherited UPDATE */ - if (newnode->commandType == CMD_UPDATE) - newnode->targetList = - adjust_inherited_tlist(newnode->targetList, - appinfo); - break; - } - } - - result = (Node *) newnode; - } - else - result = adjust_appendrel_attrs_mutator(node, &context); - - return result; -} - -static Node * -adjust_appendrel_attrs_mutator(Node *node, - adjust_appendrel_attrs_context *context) -{ - AppendRelInfo **appinfos = context->appinfos; - int nappinfos = context->nappinfos; - int cnt; - - if (node == NULL) - return NULL; - if (IsA(node, Var)) - { - Var *var = (Var *) copyObject(node); - AppendRelInfo *appinfo = NULL; - - for (cnt = 0; cnt < nappinfos; cnt++) - { - if (var->varno == appinfos[cnt]->parent_relid) - { - appinfo = appinfos[cnt]; - break; - } - } - - if (var->varlevelsup == 0 && appinfo) - { - var->varno = appinfo->child_relid; - var->varnoold = appinfo->child_relid; - if (var->varattno > 0) - { - Node *newnode; - - if (var->varattno > list_length(appinfo->translated_vars)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - var->varattno, get_rel_name(appinfo->parent_reloid)); - newnode = copyObject(list_nth(appinfo->translated_vars, - var->varattno - 1)); - if (newnode == NULL) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - var->varattno, get_rel_name(appinfo->parent_reloid)); - return newnode; - } - else if (var->varattno == 0) - { - /* - * Whole-row Var: if we are dealing with named rowtypes, we - * can use a whole-row Var for the child table plus a coercion - * step to convert the tuple layout to the parent's rowtype. - * Otherwise we have to generate a RowExpr. - */ - if (OidIsValid(appinfo->child_reltype)) - { - Assert(var->vartype == appinfo->parent_reltype); - if (appinfo->parent_reltype != appinfo->child_reltype) - { - ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr); - - r->arg = (Expr *) var; - r->resulttype = appinfo->parent_reltype; - r->convertformat = COERCE_IMPLICIT_CAST; - r->location = -1; - /* Make sure the Var node has the right type ID, too */ - var->vartype = appinfo->child_reltype; - return (Node *) r; - } - } - else - { - /* - * Build a RowExpr containing the translated variables. - * - * In practice var->vartype will always be RECORDOID here, - * so we need to come up with some suitable column names. - * We use the parent RTE's column names. - * - * Note: we can't get here for inheritance cases, so there - * is no need to worry that translated_vars might contain - * some dummy NULLs. - */ - RowExpr *rowexpr; - List *fields; - RangeTblEntry *rte; - - rte = rt_fetch(appinfo->parent_relid, - context->root->parse->rtable); - fields = copyObject(appinfo->translated_vars); - rowexpr = makeNode(RowExpr); - rowexpr->args = fields; - rowexpr->row_typeid = var->vartype; - rowexpr->row_format = COERCE_IMPLICIT_CAST; - rowexpr->colnames = copyObject(rte->eref->colnames); - rowexpr->location = -1; - - return (Node *) rowexpr; - } - } - /* system attributes don't need any other translation */ - } - return (Node *) var; - } - if (IsA(node, CurrentOfExpr)) - { - CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node); - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (cexpr->cvarno == appinfo->parent_relid) - { - cexpr->cvarno = appinfo->child_relid; - break; - } - } - return (Node *) cexpr; - } - if (IsA(node, RangeTblRef)) - { - RangeTblRef *rtr = (RangeTblRef *) copyObject(node); - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (rtr->rtindex == appinfo->parent_relid) - { - rtr->rtindex = appinfo->child_relid; - break; - } - } - return (Node *) rtr; - } - if (IsA(node, JoinExpr)) - { - /* Copy the JoinExpr node with correct mutation of subnodes */ - JoinExpr *j; - AppendRelInfo *appinfo; - - j = (JoinExpr *) expression_tree_mutator(node, - adjust_appendrel_attrs_mutator, - (void *) context); - /* now fix JoinExpr's rtindex (probably never happens) */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - appinfo = appinfos[cnt]; - - if (j->rtindex == appinfo->parent_relid) - { - j->rtindex = appinfo->child_relid; - break; - } - } - return (Node *) j; - } - if (IsA(node, PlaceHolderVar)) - { - /* Copy the PlaceHolderVar node with correct mutation of subnodes */ - PlaceHolderVar *phv; - - phv = (PlaceHolderVar *) expression_tree_mutator(node, - adjust_appendrel_attrs_mutator, - (void *) context); - /* now fix PlaceHolderVar's relid sets */ - if (phv->phlevelsup == 0) - phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos, - context->appinfos); - return (Node *) phv; - } - - /* - * This is needed, because inheritance_make_rel_from_joinlist needs to - * translate root->join_info_list executing make_rel_from_joinlist for a - * given child. - */ - if (IsA(node, SpecialJoinInfo)) - { - SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; - SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); - - memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); - newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, - context->nappinfos, - context->appinfos); - newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, - context->nappinfos, - context->appinfos); - newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, - context->nappinfos, - context->appinfos); - newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, - context->nappinfos, - context->appinfos); - newinfo->semi_rhs_exprs = - (List *) expression_tree_mutator((Node *) - oldinfo->semi_rhs_exprs, - adjust_appendrel_attrs_mutator, - (void *) context); - return (Node *) newinfo; - } - - /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, AppendRelInfo)); - Assert(!IsA(node, PlaceHolderInfo)); - Assert(!IsA(node, MinMaxAggInfo)); - - /* - * We have to process RestrictInfo nodes specially. (Note: although - * set_append_rel_pathlist will hide RestrictInfos in the parent's - * baserestrictinfo list from us, it doesn't hide those in joininfo.) - */ - if (IsA(node, RestrictInfo)) - { - RestrictInfo *oldinfo = (RestrictInfo *) node; - RestrictInfo *newinfo = makeNode(RestrictInfo); - - /* Copy all flat-copiable fields */ - memcpy(newinfo, oldinfo, sizeof(RestrictInfo)); - - /* Recursively fix the clause itself */ - newinfo->clause = (Expr *) - adjust_appendrel_attrs_mutator((Node *) oldinfo->clause, context); - - /* and the modified version, if an OR clause */ - newinfo->orclause = (Expr *) - adjust_appendrel_attrs_mutator((Node *) oldinfo->orclause, context); - - /* adjust relid sets too */ - newinfo->clause_relids = adjust_child_relids(oldinfo->clause_relids, - context->nappinfos, - context->appinfos); - newinfo->required_relids = adjust_child_relids(oldinfo->required_relids, - context->nappinfos, - context->appinfos); - newinfo->outer_relids = adjust_child_relids(oldinfo->outer_relids, - context->nappinfos, - context->appinfos); - newinfo->nullable_relids = adjust_child_relids(oldinfo->nullable_relids, - context->nappinfos, - context->appinfos); - newinfo->left_relids = adjust_child_relids(oldinfo->left_relids, - context->nappinfos, - context->appinfos); - newinfo->right_relids = adjust_child_relids(oldinfo->right_relids, - context->nappinfos, - context->appinfos); - - /* - * Reset cached derivative fields, since these might need to have - * different values when considering the child relation. Note we - * don't reset left_ec/right_ec: each child variable is implicitly - * equivalent to its parent, so still a member of the same EC if any. - */ - newinfo->eval_cost.startup = -1; - newinfo->norm_selec = -1; - newinfo->outer_selec = -1; - newinfo->left_em = NULL; - newinfo->right_em = NULL; - newinfo->scansel_cache = NIL; - newinfo->left_bucketsize = -1; - newinfo->right_bucketsize = -1; - newinfo->left_mcvfreq = -1; - newinfo->right_mcvfreq = -1; - - return (Node *) newinfo; - } - - /* - * NOTE: we do not need to recurse into sublinks, because they should - * already have been converted to subplans before we see them. - */ - Assert(!IsA(node, SubLink)); - Assert(!IsA(node, Query)); - - return expression_tree_mutator(node, adjust_appendrel_attrs_mutator, - (void *) context); -} - -/* - * Substitute child relids for parent relids in a Relid set. The array of - * appinfos specifies the substitutions to be performed. - */ -static Relids -adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos) -{ - Bitmapset *result = NULL; - int cnt; - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - /* Remove parent, add child */ - if (bms_is_member(appinfo->parent_relid, relids)) - { - /* Make a copy if we are changing the set. */ - if (!result) - result = bms_copy(relids); - - result = bms_del_member(result, appinfo->parent_relid); - result = bms_add_member(result, appinfo->child_relid); - } - } - - /* If we made any changes, return the modified copy. */ - if (result) - return result; - - /* Otherwise, return the original set without modification. */ - return relids; -} - -/* - * Replace any relid present in top_parent_relids with its child in - * child_relids. Members of child_relids can be multiple levels below top - * parent in the partition hierarchy. - */ -Relids -adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, - Relids child_relids, Relids top_parent_relids) -{ - AppendRelInfo **appinfos; - int nappinfos; - Relids parent_relids = NULL; - Relids result; - Relids tmp_result = NULL; - int cnt; - - /* - * If the given relids set doesn't contain any of the top parent relids, - * it will remain unchanged. - */ - if (!bms_overlap(relids, top_parent_relids)) - return relids; - - appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); - - /* Construct relids set for the immediate parent of the given child. */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); - } - - /* Recurse if immediate parent is not the top parent. */ - if (!bms_equal(parent_relids, top_parent_relids)) - { - tmp_result = adjust_child_relids_multilevel(root, relids, - parent_relids, - top_parent_relids); - relids = tmp_result; - } - - result = adjust_child_relids(relids, nappinfos, appinfos); - - /* Free memory consumed by any intermediate result. */ - if (tmp_result) - bms_free(tmp_result); - bms_free(parent_relids); - pfree(appinfos); - - return result; -} - -/* - * Adjust the targetlist entries of an inherited UPDATE operation - * - * The expressions have already been fixed, but we have to make sure that - * the target resnos match the child table (they may not, in the case of - * a column that was added after-the-fact by ALTER TABLE). In some cases - * this can force us to re-order the tlist to preserve resno ordering. - * (We do all this work in special cases so that preptlist.c is fast for - * the typical case.) - * - * The given tlist has already been through expression_tree_mutator; - * therefore the TargetEntry nodes are fresh copies that it's okay to - * scribble on. - * - * Note that this is not needed for INSERT because INSERT isn't inheritable. - */ -static List * -adjust_inherited_tlist(List *tlist, AppendRelInfo *context) -{ - bool changed_it = false; - ListCell *tl; - List *new_tlist; - bool more; - int attrno; - - /* This should only happen for an inheritance case, not UNION ALL */ - Assert(OidIsValid(context->parent_reloid)); - - /* Scan tlist and update resnos to match attnums of child rel */ - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - Var *childvar; - - if (tle->resjunk) - continue; /* ignore junk items */ - - /* Look up the translation of this column: it must be a Var */ - if (tle->resno <= 0 || - tle->resno > list_length(context->translated_vars)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - tle->resno, get_rel_name(context->parent_reloid)); - childvar = (Var *) list_nth(context->translated_vars, tle->resno - 1); - if (childvar == NULL || !IsA(childvar, Var)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - tle->resno, get_rel_name(context->parent_reloid)); - - if (tle->resno != childvar->varattno) - { - tle->resno = childvar->varattno; - changed_it = true; - } - } - - /* - * If we changed anything, re-sort the tlist by resno, and make sure - * resjunk entries have resnos above the last real resno. The sort - * algorithm is a bit stupid, but for such a seldom-taken path, small is - * probably better than fast. - */ - if (!changed_it) - return tlist; - - new_tlist = NIL; - more = true; - for (attrno = 1; more; attrno++) - { - more = false; - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - - if (tle->resjunk) - continue; /* ignore junk items */ - - if (tle->resno == attrno) - new_tlist = lappend(new_tlist, tle); - else if (tle->resno > attrno) - more = true; - } - } - - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - - if (!tle->resjunk) - continue; /* here, ignore non-junk items */ - - tle->resno = attrno; - new_tlist = lappend(new_tlist, tle); - attrno++; - } - - return new_tlist; -} - -/* - * adjust_appendrel_attrs_multilevel - * Apply Var translations from a toplevel appendrel parent down to a child. - * - * In some cases we need to translate expressions referencing a parent relation - * to reference an appendrel child that's multiple levels removed from it. - */ -Node * -adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, - Relids child_relids, - Relids top_parent_relids) -{ - AppendRelInfo **appinfos; - Bitmapset *parent_relids = NULL; - int nappinfos; - int cnt; - - Assert(bms_num_members(child_relids) == bms_num_members(top_parent_relids)); - - appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); - - /* Construct relids set for the immediate parent of given child. */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); - } - - /* Recurse if immediate parent is not the top parent. */ - if (!bms_equal(parent_relids, top_parent_relids)) - node = adjust_appendrel_attrs_multilevel(root, node, parent_relids, - top_parent_relids); - - /* Now translate for this child */ - node = adjust_appendrel_attrs(root, node, nappinfos, appinfos); - - pfree(appinfos); - - return node; -} - -/* - * Construct the SpecialJoinInfo for a child-join by translating - * SpecialJoinInfo for the join between parents. left_relids and right_relids - * are the relids of left and right side of the join respectively. - */ -SpecialJoinInfo * -build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, - Relids left_relids, Relids right_relids) -{ - SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo); - AppendRelInfo **left_appinfos; - int left_nappinfos; - AppendRelInfo **right_appinfos; - int right_nappinfos; - - memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo)); - left_appinfos = find_appinfos_by_relids(root, left_relids, - &left_nappinfos); - right_appinfos = find_appinfos_by_relids(root, right_relids, - &right_nappinfos); - - sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand, - left_nappinfos, left_appinfos); - sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand, - right_nappinfos, - right_appinfos); - sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand, - left_nappinfos, left_appinfos); - sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand, - right_nappinfos, - right_appinfos); - sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root, - (Node *) sjinfo->semi_rhs_exprs, - right_nappinfos, - right_appinfos); - - pfree(left_appinfos); - pfree(right_appinfos); - - return sjinfo; -} - -/* - * find_appinfos_by_relids - * Find AppendRelInfo structures for all relations specified by relids. - * - * The AppendRelInfos are returned in an array, which can be pfree'd by the - * caller. *nappinfos is set to the number of entries in the array. - */ -AppendRelInfo ** -find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos) -{ - AppendRelInfo **appinfos; - int cnt = 0; - int i; - - *nappinfos = bms_num_members(relids); - appinfos = (AppendRelInfo **) palloc(sizeof(AppendRelInfo *) * *nappinfos); - - i = -1; - while ((i = bms_next_member(relids, i)) >= 0) - { - AppendRelInfo *appinfo = root->append_rel_array[i]; - - if (!appinfo) - elog(ERROR, "child rel %d not found in append_rel_array", i); - - appinfos[cnt++] = appinfo; - } - return appinfos; -} diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile index da9ccf32b4..d166030a10 100644 --- a/src/backend/optimizer/util/Makefile +++ b/src/backend/optimizer/util/Makefile @@ -12,7 +12,8 @@ subdir = src/backend/optimizer/util top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = clauses.o joininfo.o orclauses.o pathnode.o placeholder.o \ - plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o +OBJS = append.o appendinfo.o clauses.o joininfo.o orclauses.o pathnode.o \ + placeholder.o plancat.o predtest.o relnode.o restrictinfo.o tlist.o \ + var.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/optimizer/util/append.c b/src/backend/optimizer/util/append.c new file mode 100644 index 0000000000..d2ad80034f --- /dev/null +++ b/src/backend/optimizer/util/append.c @@ -0,0 +1,777 @@ +/*------------------------------------------------------------------------- + * + * append.c + * Routines to process children of an appendrel parent + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/optimizer/path/append.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <limits.h> + +#include "access/sysattr.h" +#include "catalog/partition.h" +#include "catalog/pg_class.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_type.h" +#include "nodes/makefuncs.h" +#include "optimizer/append.h" +#include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" +#include "optimizer/plancat.h" +#include "optimizer/planmain.h" +#include "optimizer/planner.h" +#include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" +#include "optimizer/tlist.h" +#include "optimizer/var.h" +#include "partitioning/partprune.h" +#include "utils/rel.h" + +static void expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti); +static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, + Index rti, RelOptInfo *rel); +static void expand_partitioned_rtentry(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p); +static RelOptInfo *build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); + + +/* + * expand_inherited_tables + * Expand each rangetable entry that represents an inheritance set + * into an "append relation". At the conclusion of this process, + * the "inh" flag is set in all and only those RTEs that are append + * relation parents. + */ +void +expand_inherited_tables(PlannerInfo *root) +{ + int orig_rtable_size; + Index rti; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; + + /* + * expand_append_rtentry may add RTEs to parse->rtable. The function is + * expected to recursively handle any RTEs that it creates with inh=true. + * So just scan as far as the original end of the rtable list. + */ + for (rti = 1; rti < orig_rtable_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; + + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + if (rte->inh) + expand_append_rtentry(root, brel, rte, rti); + } +} + +/* + * expand_append_rtentry + * This initializes RelOptInfos for an appendrel's child relations, if + * any + * + * 'rel' is the appendrel parent, whose range table entry ('rte') has been + * marked to require adding children. An appendrel parent could either + * be a subquery (if we flattened UNION ALL query) or a table that's known + * to have inheritance children. The latter consists of both regular + * inheritance parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as an appendrel child. The original RTE is + * considered to represent the whole inheritance set. + */ +static void +expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, + Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_append_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_inherited_rtentry(root, rte, rti, rel); + } +} + +/* + * expand_inherited_rtentry + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. + * + * Note that the original RTE is considered to represent the whole + * inheritance set. The first of the generated RTEs is an RTE for the same + * table, but with inh = false, to represent the parent table in its role + * as a simple member of the inheritance set. + * + * A childless table is never considered to be an inheritance set. For + * regular inheritance, a parent RTE must always have at least two associated + * AppendRelInfos: one corresponding to the parent table as a simple member of + * inheritance set and one or more corresponding to the actual children. + */ +static void +expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, + RelOptInfo *rel) +{ + Oid parentOID; + PlanRowMark *oldrc; + LOCKMODE lockmode = rte->rellockmode; + List *inhOIDs; + ListCell *l; + int num_children; + int num_children_added = 0; + + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); + parentOID = rte->relid; + + /* Scan for all members of inheritance set, acquire needed locks */ + inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); + + /* + * Check that there's at least one descendant, else treat as no-child + * case. This could happen despite above has_subclass() check, if table + * once had a child but no longer does. + */ + num_children = list_length(inhOIDs); + if (num_children < 2) + { + /* Clear flag before returning */ + rte->inh = false; + return; + } + + /* + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. + */ + oldrc = get_plan_rowmark(root->rowMarks, rti); + Assert(oldrc == NULL || oldrc->isParent); + + /* + * Must expand PlannerInfo arrays by num_children before we can add + * children. + */ + expand_planner_arrays(root, num_children); + + foreach(l, inhOIDs) + { + Oid childOID = lfirst_oid(l); + Relation newrelation; + RangeTblEntry *childrte; + Index childRTindex; + + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + + /* + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. + */ + if (RELATION_IS_OTHER_TEMP(newrelation)) + { + heap_close(newrelation, lockmode); + continue; + } + + (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation, &childrte, + &childRTindex); + Assert(childrte != NULL); + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + Assert(childRTindex > 0); + + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; + } + + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } +} + +/* + * expand_partitioned_rtentry + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * in order to avoid partitions being locked in a different order than other + * places in the backend that may lock partitions. + */ +static void +expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel) +{ + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; + int i; + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; + + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); + + /* + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. + */ + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); + + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + expand_planner_arrays(root, bms_num_members(partindexes)); + + /* + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. + */ + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); + + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) + { + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + RangeTblEntry *childrte; + Index childRTindex; + + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); + + /* + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. + */ + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } + + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation, + &childrte, &childRTindex); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; + + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + + /* If the child is partitioned itself, expand it too. */ + if (childrel->part_scheme) + { + Assert(childrte->inh); + expand_partitioned_rtentry(root, childrte, childRTindex, + childrel); + } + } + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); + } +} + +/* + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. + * + * The return value is the RelOptInfo that's added. + * + * PlanRowMarks still carry the top-parent's RTI, and the top-parent's + * allMarkTypes field still accumulates values from all descendents. + * + * "parentrte" and "parentRTindex" are immediate parent's RTE and + * RTI. "top_parentrc" is top parent's PlanRowMark. + */ +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p) +{ + Query *parse = root->parse; + Oid childOID = RelationGetRelid(childrel); + RangeTblEntry *childrte; + Index childRTindex; + AppendRelInfo *appinfo; + RelOptInfo *childrelopt; + + /* + * Build an RTE for the child, and attach to query's rangetable list. We + * copy most fields of the parent's RTE, but replace relation OID and + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties + * individual children may have. (This is an intentional choice to make + * inherited RLS work like regular permissions checks.) The parent + * securityQuals will be propagated to children along with other base + * restriction clauses, so we don't need to do it here. + */ + childrte = copyObject(parentrte); + *childrte_p = childrte; + childrte->relid = childOID; + childrte->relkind = childrel->rd_rel->relkind; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); + childrte->requiredPerms = 0; + childrte->securityQuals = NIL; + parse->rtable = lappend(parse->rtable, childrte); + childRTindex = list_length(parse->rtable); + *childRTindex_p = childRTindex; + + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + childrel->rd_att, + childOID, + childrel->rd_rel->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); + + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); + } + + /* + * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE. + */ + if (top_parentrc) + { + PlanRowMark *childrc = makeNode(PlanRowMark); + + childrc->rti = childRTindex; + childrc->prti = top_parentrc->rti; + childrc->rowmarkId = top_parentrc->rowmarkId; + /* Reselect rowmark type, because relkind might not match parent */ + childrc->markType = select_rowmark_type(childrte, + top_parentrc->strength); + childrc->allMarkTypes = (1 << childrc->markType); + childrc->strength = top_parentrc->strength; + childrc->waitPolicy = top_parentrc->waitPolicy; + + /* + * We mark RowMarks for partitioned child tables as parent RowMarks so + * that the executor ignores them (except their existence means that + * the child tables be locked using appropriate mode). + */ + childrc->isParent = (childrte->relkind == RELKIND_PARTITIONED_TABLE); + + /* Include child's rowmark type in top parent's allMarkTypes */ + top_parentrc->allMarkTypes |= childrc->allMarkTypes; + + root->rowMarks = lappend(root->rowMarks, childrc); + } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need need to create + * one, because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_append_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; +} + +/* + * build_append_child_rel + * Build a RelOptInfo for child relation of an append rel + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields base on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turns out to be self-contradictory, the + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + List *childquals; + ListCell *lc; + bool have_const_false_cq; + Index cq_min_security; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = false; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, parent->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual = (Node *) rinfo->clause; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, childqual, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might + * have securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals.) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except + * that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* + * not likely that we'd see constants here, so no + * check + */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, + false, + false, + security_level, + NULL, NULL, + NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* Set child's version of baserestrictinfo. */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; +} diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c new file mode 100644 index 0000000000..9502535e53 --- /dev/null +++ b/src/backend/optimizer/util/appendinfo.c @@ -0,0 +1,851 @@ +/*------------------------------------------------------------------------- + * + * appendinfo.c + * Routines for mapping between append parent(s) and children + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/optimizer/path/appendinfo.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" +#include "parser/parsetree.h" +#include "utils/rel.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" + +typedef struct +{ + PlannerInfo *root; + int nappinfos; + AppendRelInfo **appinfos; +} adjust_appendrel_attrs_context; + +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); +static Node *adjust_appendrel_attrs_mutator(Node *node, + adjust_appendrel_attrs_context *context); +static Relids adjust_child_relids(Relids relids, int nappinfos, + AppendRelInfo **appinfos); +static List *adjust_inherited_tlist(List *tlist, + AppendRelInfo *context); + +AppendRelInfo * +make_append_rel_info(RelOptInfo *parentrel, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex) +{ + AppendRelInfo *appinfo = makeNode(AppendRelInfo); + + appinfo->parent_relid = parentrel->relid; + appinfo->child_relid = childRTindex; + appinfo->parent_reltype = parentrel->reltype; + appinfo->child_reltype = childreltype; + make_inh_translation_list(parentrel->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, + &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; + + return appinfo; +} + +/* + * adjust_appendrel_attrs + * Copy the specified query or expression and translate Vars referring to a + * parent rel to refer to the corresponding child rel instead. We also + * update rtindexes appearing outside Vars, such as resultRelation and + * jointree relids. + * + * Note: this is only applied after conversion of sublinks to subplans, + * so we don't need to cope with recursion into sub-queries. + * + * Note: this is not hugely different from what pullup_replace_vars() does; + * maybe we should try to fold the two routines together. + */ +Node * +adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, + AppendRelInfo **appinfos) +{ + Node *result; + adjust_appendrel_attrs_context context; + + context.root = root; + context.nappinfos = nappinfos; + context.appinfos = appinfos; + + /* If there's nothing to adjust, don't call this function. */ + Assert(nappinfos >= 1 && appinfos != NULL); + + /* + * Must be prepared to start with a Query or a bare expression tree. + */ + if (node && IsA(node, Query)) + { + Query *newnode; + int cnt; + + newnode = query_tree_mutator((Query *) node, + adjust_appendrel_attrs_mutator, + (void *) &context, + QTW_IGNORE_RC_SUBQUERIES); + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (newnode->resultRelation == appinfo->parent_relid) + { + newnode->resultRelation = appinfo->child_relid; + /* Fix tlist resnos too, if it's inherited UPDATE */ + if (newnode->commandType == CMD_UPDATE) + newnode->targetList = + adjust_inherited_tlist(newnode->targetList, + appinfo); + break; + } + } + + result = (Node *) newnode; + } + else + result = adjust_appendrel_attrs_mutator(node, &context); + + return result; +} + +static Node * +adjust_appendrel_attrs_mutator(Node *node, + adjust_appendrel_attrs_context *context) +{ + AppendRelInfo **appinfos = context->appinfos; + int nappinfos = context->nappinfos; + int cnt; + + if (node == NULL) + return NULL; + if (IsA(node, Var)) + { + Var *var = (Var *) copyObject(node); + AppendRelInfo *appinfo = NULL; + + for (cnt = 0; cnt < nappinfos; cnt++) + { + if (var->varno == appinfos[cnt]->parent_relid) + { + appinfo = appinfos[cnt]; + break; + } + } + + if (var->varlevelsup == 0 && appinfo) + { + var->varno = appinfo->child_relid; + var->varnoold = appinfo->child_relid; + if (var->varattno > 0) + { + Node *newnode; + + if (var->varattno > list_length(appinfo->translated_vars)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + var->varattno, get_rel_name(appinfo->parent_reloid)); + newnode = copyObject(list_nth(appinfo->translated_vars, + var->varattno - 1)); + if (newnode == NULL) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + var->varattno, get_rel_name(appinfo->parent_reloid)); + return newnode; + } + else if (var->varattno == 0) + { + /* + * Whole-row Var: if we are dealing with named rowtypes, we + * can use a whole-row Var for the child table plus a coercion + * step to convert the tuple layout to the parent's rowtype. + * Otherwise we have to generate a RowExpr. + */ + if (OidIsValid(appinfo->child_reltype)) + { + Assert(var->vartype == appinfo->parent_reltype); + if (appinfo->parent_reltype != appinfo->child_reltype) + { + ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr); + + r->arg = (Expr *) var; + r->resulttype = appinfo->parent_reltype; + r->convertformat = COERCE_IMPLICIT_CAST; + r->location = -1; + /* Make sure the Var node has the right type ID, too */ + var->vartype = appinfo->child_reltype; + return (Node *) r; + } + } + else + { + /* + * Build a RowExpr containing the translated variables. + * + * In practice var->vartype will always be RECORDOID here, + * so we need to come up with some suitable column names. + * We use the parent RTE's column names. + * + * Note: we can't get here for inheritance cases, so there + * is no need to worry that translated_vars might contain + * some dummy NULLs. + */ + RowExpr *rowexpr; + List *fields; + RangeTblEntry *rte; + + rte = rt_fetch(appinfo->parent_relid, + context->root->parse->rtable); + fields = copyObject(appinfo->translated_vars); + rowexpr = makeNode(RowExpr); + rowexpr->args = fields; + rowexpr->row_typeid = var->vartype; + rowexpr->row_format = COERCE_IMPLICIT_CAST; + rowexpr->colnames = copyObject(rte->eref->colnames); + rowexpr->location = -1; + + return (Node *) rowexpr; + } + } + /* system attributes don't need any other translation */ + } + return (Node *) var; + } + if (IsA(node, CurrentOfExpr)) + { + CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node); + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (cexpr->cvarno == appinfo->parent_relid) + { + cexpr->cvarno = appinfo->child_relid; + break; + } + } + return (Node *) cexpr; + } + if (IsA(node, RangeTblRef)) + { + RangeTblRef *rtr = (RangeTblRef *) copyObject(node); + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (rtr->rtindex == appinfo->parent_relid) + { + rtr->rtindex = appinfo->child_relid; + break; + } + } + return (Node *) rtr; + } + if (IsA(node, JoinExpr)) + { + /* Copy the JoinExpr node with correct mutation of subnodes */ + JoinExpr *j; + AppendRelInfo *appinfo; + + j = (JoinExpr *) expression_tree_mutator(node, + adjust_appendrel_attrs_mutator, + (void *) context); + /* now fix JoinExpr's rtindex (probably never happens) */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + appinfo = appinfos[cnt]; + + if (j->rtindex == appinfo->parent_relid) + { + j->rtindex = appinfo->child_relid; + break; + } + } + return (Node *) j; + } + if (IsA(node, PlaceHolderVar)) + { + /* Copy the PlaceHolderVar node with correct mutation of subnodes */ + PlaceHolderVar *phv; + + phv = (PlaceHolderVar *) expression_tree_mutator(node, + adjust_appendrel_attrs_mutator, + (void *) context); + /* now fix PlaceHolderVar's relid sets */ + if (phv->phlevelsup == 0) + phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos, + context->appinfos); + return (Node *) phv; + } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + + /* Shouldn't need to handle planner auxiliary nodes here */ + Assert(!IsA(node, AppendRelInfo)); + Assert(!IsA(node, PlaceHolderInfo)); + Assert(!IsA(node, MinMaxAggInfo)); + + /* + * We have to process RestrictInfo nodes specially. (Note: although + * set_append_rel_pathlist will hide RestrictInfos in the parent's + * baserestrictinfo list from us, it doesn't hide those in joininfo.) + */ + if (IsA(node, RestrictInfo)) + { + RestrictInfo *oldinfo = (RestrictInfo *) node; + RestrictInfo *newinfo = makeNode(RestrictInfo); + + /* Copy all flat-copiable fields */ + memcpy(newinfo, oldinfo, sizeof(RestrictInfo)); + + /* Recursively fix the clause itself */ + newinfo->clause = (Expr *) + adjust_appendrel_attrs_mutator((Node *) oldinfo->clause, context); + + /* and the modified version, if an OR clause */ + newinfo->orclause = (Expr *) + adjust_appendrel_attrs_mutator((Node *) oldinfo->orclause, context); + + /* adjust relid sets too */ + newinfo->clause_relids = adjust_child_relids(oldinfo->clause_relids, + context->nappinfos, + context->appinfos); + newinfo->required_relids = adjust_child_relids(oldinfo->required_relids, + context->nappinfos, + context->appinfos); + newinfo->outer_relids = adjust_child_relids(oldinfo->outer_relids, + context->nappinfos, + context->appinfos); + newinfo->nullable_relids = adjust_child_relids(oldinfo->nullable_relids, + context->nappinfos, + context->appinfos); + newinfo->left_relids = adjust_child_relids(oldinfo->left_relids, + context->nappinfos, + context->appinfos); + newinfo->right_relids = adjust_child_relids(oldinfo->right_relids, + context->nappinfos, + context->appinfos); + + /* + * Reset cached derivative fields, since these might need to have + * different values when considering the child relation. Note we + * don't reset left_ec/right_ec: each child variable is implicitly + * equivalent to its parent, so still a member of the same EC if any. + */ + newinfo->eval_cost.startup = -1; + newinfo->norm_selec = -1; + newinfo->outer_selec = -1; + newinfo->left_em = NULL; + newinfo->right_em = NULL; + newinfo->scansel_cache = NIL; + newinfo->left_bucketsize = -1; + newinfo->right_bucketsize = -1; + newinfo->left_mcvfreq = -1; + newinfo->right_mcvfreq = -1; + + return (Node *) newinfo; + } + + /* + * NOTE: we do not need to recurse into sublinks, because they should + * already have been converted to subplans before we see them. + */ + Assert(!IsA(node, SubLink)); + Assert(!IsA(node, Query)); + + return expression_tree_mutator(node, adjust_appendrel_attrs_mutator, + (void *) context); +} + +/* + * Substitute child relids for parent relids in a Relid set. The array of + * appinfos specifies the substitutions to be performed. + */ +static Relids +adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos) +{ + Bitmapset *result = NULL; + int cnt; + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + /* Remove parent, add child */ + if (bms_is_member(appinfo->parent_relid, relids)) + { + /* Make a copy if we are changing the set. */ + if (!result) + result = bms_copy(relids); + + result = bms_del_member(result, appinfo->parent_relid); + result = bms_add_member(result, appinfo->child_relid); + } + } + + /* If we made any changes, return the modified copy. */ + if (result) + return result; + + /* Otherwise, return the original set without modification. */ + return relids; +} + +/* + * Replace any relid present in top_parent_relids with its child in + * child_relids. Members of child_relids can be multiple levels below top + * parent in the partition hierarchy. + */ +Relids +adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, + Relids child_relids, Relids top_parent_relids) +{ + AppendRelInfo **appinfos; + int nappinfos; + Relids parent_relids = NULL; + Relids result; + Relids tmp_result = NULL; + int cnt; + + /* + * If the given relids set doesn't contain any of the top parent relids, + * it will remain unchanged. + */ + if (!bms_overlap(relids, top_parent_relids)) + return relids; + + appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); + + /* Construct relids set for the immediate parent of the given child. */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); + } + + /* Recurse if immediate parent is not the top parent. */ + if (!bms_equal(parent_relids, top_parent_relids)) + { + tmp_result = adjust_child_relids_multilevel(root, relids, + parent_relids, + top_parent_relids); + relids = tmp_result; + } + + result = adjust_child_relids(relids, nappinfos, appinfos); + + /* Free memory consumed by any intermediate result. */ + if (tmp_result) + bms_free(tmp_result); + bms_free(parent_relids); + pfree(appinfos); + + return result; +} + +/* + * adjust_appendrel_attrs_multilevel + * Apply Var translations from a toplevel appendrel parent down to a child. + * + * In some cases we need to translate expressions referencing a parent relation + * to reference an appendrel child that's multiple levels removed from it. + */ +Node * +adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, + Relids child_relids, + Relids top_parent_relids) +{ + AppendRelInfo **appinfos; + Bitmapset *parent_relids = NULL; + int nappinfos; + int cnt; + + Assert(bms_num_members(child_relids) == bms_num_members(top_parent_relids)); + + appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); + + /* Construct relids set for the immediate parent of given child. */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); + } + + /* Recurse if immediate parent is not the top parent. */ + if (!bms_equal(parent_relids, top_parent_relids)) + node = adjust_appendrel_attrs_multilevel(root, node, parent_relids, + top_parent_relids); + + /* Now translate for this child */ + node = adjust_appendrel_attrs(root, node, nappinfos, appinfos); + + pfree(appinfos); + + return node; +} + +/* + * Construct the SpecialJoinInfo for a child-join by translating + * SpecialJoinInfo for the join between parents. left_relids and right_relids + * are the relids of left and right side of the join respectively. + */ +SpecialJoinInfo * +build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, + Relids left_relids, Relids right_relids) +{ + SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo); + AppendRelInfo **left_appinfos; + int left_nappinfos; + AppendRelInfo **right_appinfos; + int right_nappinfos; + + memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo)); + left_appinfos = find_appinfos_by_relids(root, left_relids, + &left_nappinfos); + right_appinfos = find_appinfos_by_relids(root, right_relids, + &right_nappinfos); + + sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand, + left_nappinfos, left_appinfos); + sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand, + right_nappinfos, + right_appinfos); + sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand, + left_nappinfos, left_appinfos); + sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand, + right_nappinfos, + right_appinfos); + sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root, + (Node *) sjinfo->semi_rhs_exprs, + right_nappinfos, + right_appinfos); + + pfree(left_appinfos); + pfree(right_appinfos); + + return sjinfo; +} + +/* + * find_appinfos_by_relids + * Find AppendRelInfo structures for all relations specified by relids. + * + * The AppendRelInfos are returned in an array, which can be pfree'd by the + * caller. *nappinfos is set to the number of entries in the array. + */ +AppendRelInfo ** +find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos) +{ + AppendRelInfo **appinfos; + int cnt = 0; + int i; + + *nappinfos = bms_num_members(relids); + appinfos = (AppendRelInfo **) palloc(sizeof(AppendRelInfo *) * *nappinfos); + + i = -1; + while ((i = bms_next_member(relids, i)) >= 0) + { + AppendRelInfo *appinfo = root->append_rel_array[i]; + + if (!appinfo) + elog(ERROR, "child rel %d not found in append_rel_array", i); + + appinfos[cnt++] = appinfo; + } + return appinfos; +} + +/* + * make_inh_translation_list + * Build the list of translations from parent Vars to child Vars for + * an inheritance child. + * + * For paranoia's sake, we match type/collation as well as attribute name. + */ +static void +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) +{ + List *vars = NIL; + int oldnatts = old_tupdesc->natts; + int newnatts = new_tupdesc->natts; + int old_attno; + int new_attno = 0; + + for (old_attno = 0; old_attno < oldnatts; old_attno++) + { + Form_pg_attribute att; + char *attname; + Oid atttypid; + int32 atttypmod; + Oid attcollation; + + att = TupleDescAttr(old_tupdesc, old_attno); + if (att->attisdropped) + { + /* Just put NULL into this list entry */ + vars = lappend(vars, NULL); + continue; + } + attname = NameStr(att->attname); + atttypid = att->atttypid; + atttypmod = att->atttypmod; + attcollation = att->attcollation; + + /* + * When we are generating the "translation list" for the parent table + * of an inheritance set, no need to search for matches. + */ + if (from_rel == to_rel) + { + vars = lappend(vars, makeVar(newvarno, + (AttrNumber) (old_attno + 1), + atttypid, + atttypmod, + attcollation, + 0)); + continue; + } + + /* + * Otherwise we have to search for the matching column by name. + * There's no guarantee it'll have the same column position, because + * of cases like ALTER TABLE ADD COLUMN and multiple inheritance. + * However, in simple cases, the relative order of columns is mostly + * the same in both relations, so try the column of newrelation that + * follows immediately after the one that we just found, and if that + * fails, let syscache handle it. + */ + if (new_attno >= newnatts || + (att = TupleDescAttr(new_tupdesc, new_attno))->attisdropped || + strcmp(attname, NameStr(att->attname)) != 0) + { + HeapTuple newtup; + + newtup = SearchSysCacheAttName(to_rel, attname); + if (!newtup) + elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", + attname, get_rel_name(to_rel)); + new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; + ReleaseSysCache(newtup); + + att = TupleDescAttr(new_tupdesc, new_attno); + } + + /* Found it, check type and collation match */ + if (atttypid != att->atttypid || atttypmod != att->atttypmod) + elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", + attname, get_rel_name(to_rel)); + if (attcollation != att->attcollation) + elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", + attname, get_rel_name(to_rel)); + + vars = lappend(vars, makeVar(newvarno, + (AttrNumber) (new_attno + 1), + atttypid, + atttypmod, + attcollation, + 0)); + new_attno++; + } + + *translated_vars = vars; +} + +/* + * translate_col_privs + * Translate a bitmapset representing per-column privileges from the + * parent rel's attribute numbering to the child's. + * + * The only surprise here is that we don't translate a parent whole-row + * reference into a child whole-row reference. That would mean requiring + * permissions on all child columns, which is overly strict, since the + * query is really only going to reference the inherited columns. Instead + * we set the per-column bits for all inherited columns. + */ +Bitmapset * +translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars) +{ + Bitmapset *child_privs = NULL; + bool whole_row; + int attno; + ListCell *lc; + + /* System attributes have the same numbers in all tables */ + for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) + { + if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + attno - FirstLowInvalidHeapAttributeNumber); + } + + /* Check if parent has whole-row reference */ + whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, + parent_privs); + + /* And now translate the regular user attributes, using the vars list */ + attno = InvalidAttrNumber; + foreach(lc, translated_vars) + { + Var *var = lfirst_node(Var, lc); + + attno++; + if (var == NULL) /* ignore dropped columns */ + continue; + if (whole_row || + bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + var->varattno - FirstLowInvalidHeapAttributeNumber); + } + + return child_privs; +} + +/* + * Adjust the targetlist entries of an inherited UPDATE operation + * + * The expressions have already been fixed, but we have to make sure that + * the target resnos match the child table (they may not, in the case of + * a column that was added after-the-fact by ALTER TABLE). In some cases + * this can force us to re-order the tlist to preserve resno ordering. + * (We do all this work in special cases so that preptlist.c is fast for + * the typical case.) + * + * The given tlist has already been through expression_tree_mutator; + * therefore the TargetEntry nodes are fresh copies that it's okay to + * scribble on. + * + * Note that this is not needed for INSERT because INSERT isn't inheritable. + */ +static List * +adjust_inherited_tlist(List *tlist, AppendRelInfo *context) +{ + bool changed_it = false; + ListCell *tl; + List *new_tlist; + bool more; + int attrno; + + /* This should only happen for an inheritance case, not UNION ALL */ + Assert(OidIsValid(context->parent_reloid)); + + /* Scan tlist and update resnos to match attnums of child rel */ + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + Var *childvar; + + if (tle->resjunk) + continue; /* ignore junk items */ + + /* Look up the translation of this column: it must be a Var */ + if (tle->resno <= 0 || + tle->resno > list_length(context->translated_vars)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + tle->resno, get_rel_name(context->parent_reloid)); + childvar = (Var *) list_nth(context->translated_vars, tle->resno - 1); + if (childvar == NULL || !IsA(childvar, Var)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + tle->resno, get_rel_name(context->parent_reloid)); + + if (tle->resno != childvar->varattno) + { + tle->resno = childvar->varattno; + changed_it = true; + } + } + + /* + * If we changed anything, re-sort the tlist by resno, and make sure + * resjunk entries have resnos above the last real resno. The sort + * algorithm is a bit stupid, but for such a seldom-taken path, small is + * probably better than fast. + */ + if (!changed_it) + return tlist; + + new_tlist = NIL; + more = true; + for (attrno = 1; more; attrno++) + { + more = false; + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + + if (tle->resjunk) + continue; /* ignore junk items */ + + if (tle->resno == attrno) + new_tlist = lappend(new_tlist, tle); + else if (tle->resno > attrno) + more = true; + } + } + + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + + if (!tle->resjunk) + continue; /* here, ignore non-junk items */ + + tle->resno = attrno; + new_tlist = lappend(new_tlist, tle); + attrno++; + } + + return new_tlist; +} diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index d50d86b252..8ce88876c4 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -20,6 +20,7 @@ #include "foreign/fdwapi.h" #include "nodes/extensible.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index c23db9d78d..52a11f434f 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -17,6 +17,7 @@ #include <limits.h> #include "miscadmin.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index fcf8d6032c..e76906da1f 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -44,6 +44,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/include/optimizer/append.h b/src/include/optimizer/append.h new file mode 100644 index 0000000000..b5f025c137 --- /dev/null +++ b/src/include/optimizer/append.h @@ -0,0 +1,25 @@ +/*------------------------------------------------------------------------- + * + * append.h + * prototypes for append.c. + * + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/optimizer/append.h + * + *------------------------------------------------------------------------- + */ +#ifndef APPEND_H +#define APPEND_H + +#include "nodes/relation.h" + +/* + * append.c + * utilities for dealing with append relations + */ +extern void expand_inherited_tables(PlannerInfo *root); + +#endif /* APPEND_H */ diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h new file mode 100644 index 0000000000..e205e78e6d --- /dev/null +++ b/src/include/optimizer/appendinfo.h @@ -0,0 +1,43 @@ +/*------------------------------------------------------------------------- + * + * appendinfo.h + * Routines for mapping expressions between append rel parent(s) and + * children + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/optimizer/appendinfo.h + * + *------------------------------------------------------------------------- + */ +#ifndef APPENDINFO_H +#define APPENDINFO_H + +#include "nodes/plannodes.h" +#include "nodes/relation.h" +#include "utils/relcache.h" + +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parentrel, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex); +extern Bitmapset *translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars); +extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, + int nappinfos, AppendRelInfo **appinfos); + +extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, + Relids child_relids, + Relids top_parent_relids); + +extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root, + Relids relids, int *nappinfos); + +extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, + SpecialJoinInfo *parent_sjinfo, + Relids left_relids, Relids right_relids); +extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, + Relids child_relids, Relids top_parent_relids); + +#endif /* APPENDINFO_H */ diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index faae07d240..3ad9b4a77e 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -48,24 +48,5 @@ extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); */ extern RelOptInfo *plan_set_operations(PlannerInfo *root); -extern void expand_inherited_tables(PlannerInfo *root); -extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, - int nappinfos, AppendRelInfo **appinfos); -extern AppendRelInfo *make_append_rel_info(RelOptInfo *parentrel, - RangeTblEntry *parentrte, - TupleDesc childdesc, Oid childoid, Oid childreltype, - Index childRTindex); -extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, - Relids child_relids, - Relids top_parent_relids); - -extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root, - Relids relids, int *nappinfos); - -extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, - SpecialJoinInfo *parent_sjinfo, - Relids left_relids, Relids right_relids); -extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, - Relids child_relids, Relids top_parent_relids); #endif /* PREP_H */ -- 2.11.0
v8-0005-Teach-planner-to-only-process-unpruned-partitions.patch (text/plain, 7.2K) download From 901b9a74aafc06d2221522e1a78860298c854b75 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 15 Oct 2018 10:59:24 +0900 Subject: [PATCH v8 5/6] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/append.c | 8 ++++++++ src/backend/optimizer/util/relnode.c | 3 +++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/relation.h | 2 ++ 6 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 6e321ec9e7..76531a05cf 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1424,6 +1424,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 0586c48ed0..84aa0df6be 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6762,7 +6762,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -6770,9 +6772,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -6853,7 +6853,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -6865,7 +6864,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -6875,9 +6876,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/append.c b/src/backend/optimizer/util/append.c index d2ad80034f..3b3fb304b9 100644 --- a/src/backend/optimizer/util/append.c +++ b/src/backend/optimizer/util/append.c @@ -351,6 +351,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, /* Perform pruning. */ partindexes = prune_append_rel_partitions(parentrel); + parentrel->live_parts = partindexes; /* Must expand PlannerInfo arrays before we can add children. */ expand_planner_arrays(root, bms_num_members(partindexes)); @@ -394,6 +395,13 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, Assert(childrel != NULL); parentrel->part_rels[i] = childrel; + /* + * If partition is excluded by constraints, remove it from + * live_parts, too. + */ + if (IS_DUMMY_REL(childrel)) + parentrel->live_parts = bms_del_member(parentrel->live_parts, i); + /* Close child relations, but keep locks */ heap_close(newrelation, NoLock); diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 52a11f434f..6c7eafe6db 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1748,6 +1748,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index e76906da1f..8bc2f5c5c2 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -441,29 +441,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index e6fdbcd030..f43fa46fc5 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -727,6 +727,8 @@ typedef struct RelOptInfo Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Set of live partitions; contains indexes + * into part_rels array */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.11.0
v8-0006-Do-not-lock-all-partitions-at-the-beginning.patch (text/plain, 1.2K) download From 610ba9fadcc7fdb5aea86da67781c48b27cc0563 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 12 Oct 2018 19:12:53 +0900 Subject: [PATCH v8 6/6] Do not lock all partitions at the beginning --- src/backend/optimizer/util/append.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/backend/optimizer/util/append.c b/src/backend/optimizer/util/append.c index 3b3fb304b9..0b0b75d409 100644 --- a/src/backend/optimizer/util/append.c +++ b/src/backend/optimizer/util/append.c @@ -335,10 +335,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, parentrel->inh_root_parent : parentRTindex; - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - /* * Initialize partitioned_child_rels to contain this RT index. * @@ -374,8 +370,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, RangeTblEntry *childrte; Index childRTindex; - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); + newrelation = heap_open(childOID, lockmode); Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2018-12-05 02:29  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 1 reply; 265+ messages in thread

From: Imai, Yoshikazu @ 2018-12-05 02:29 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Hi Amit,

On Tue, Nov 20, 2018 at 10:24 PM, Amit Langote wrote:
> Attached v8 patches.

Thanks for the patch. I took a look 0003, 0005, 0006 of v8 patch.

1.
0003: line 267-268
+		 * Child relation may have be marked dummy if build_append_child_rel
+		 * found self-contradictory quals.

/s/have be marked/have been marked/

2.
0003: around line 1077
In append.c(or prepunion.c)
228      * Check that there's at least one descendant, else treat as no-child
229      * case.  This could happen despite above has_subclass() check, if table
230      * once had a child but no longer does.

has_subclass() check is moved to subquery_planner from above this code,
so the comments need to be modified like below.

s/above has_subclass() check/has_subclass() check in subquery_planner/

3.
0003: line 1241-1244
0006: line ?

In comments of expand_partitioned_rtentry:
+ * Note: even though only the unpruned partitions will be added to the
+ * resulting plan, this still locks *all* partitions via find_all_inheritors
+ * in order to avoid partitions being locked in a different order than other
+ * places in the backend that may lock partitions.

This comments is no more needed if 0006 patch is applied because
find_all_inheritors is removed in the 0006 patch.

4.
0003: line 1541-1544

+	 * Add the RelOptInfo.  Even though we may not really scan this relation
+	 * for reasons such as contradictory quals, we still need need to create
+	 * one, because for every RTE in the query's range table, there must be an
+	 * accompanying RelOptInfo.

s/need need/need/

5.
0003: line 1620-1621

+ * After creating the RelOptInfo for the given child RT index, it goes on to
+ * initialize some of its fields base on the parent RelOptInfo.

s/fields base on/fields based on/

6.
parsenodes.h
 906  *    inh is true for relation references that should be expanded to include
 907  *    inheritance children, if the rel has any.  This *must* be false for
 908  *    RTEs other than RTE_RELATION entries.

I think inh can become true now even if RTEKind equals RTE_SUBQUERY, so latter
sentence need to be modified.

7.
0005: line 109-115
+		/*
+		 * If partition is excluded by constraints, remove it from
+		 * live_parts, too.
+		 */
+		if (IS_DUMMY_REL(childrel))
+			parentrel->live_parts = bms_del_member(parentrel->live_parts, i);
+

When I read this comment, I imagined that relation_excluded_by_constraints()
would be called before this code. childrel is marked dummy if
build_append_child_rel found self-contradictory quals, so comments can be
modified more correctly like another comments in your patch as below.

In 0003: line 267-271
+		 * Child relation may have be marked dummy if build_append_child_rel
+		 * found self-contradictory quals.
+		 */
+		if (IS_DUMMY_REL(childrel))
+			continue;

8.
0003: line 705-711
+		 * Query planning may have added some columns to the top-level tlist,
+		 * which happens when there are row marks applied to inheritance
+		 * parent relations (additional junk columns needed for applying row
+		 * marks are added after expanding inheritance.)
+		 */
+		if (list_length(tlist) < list_length(root->processed_tlist))
+			tlist = root->processed_tlist;

In grouping_planner():

  if (planned_rel == NULL)
  {
      ...
      root->processed_tlist = tlist;
  }
  else
      tlist = root->processed_tlist;
  ...
  if (current_rel == NULL)
      current_rel = query_planner(root, tlist,
                                  standard_qp_callback, &qp_extra);
  ...
  /*
   * Query planning may have added some columns to the top-level tlist,
   * which happens when there are row marks applied to inheritance
   * parent relations (additional junk columns needed for applying row
   * marks are added after expanding inheritance.)
   */
  if (list_length(tlist) < list_length(root->processed_tlist))
      tlist = root->processed_tlist;


Are there any case tlist points to an address different from
root->processed_tlist after calling query_planner? Junk columns are possibly
added to root->processed_tlist after expanding inheritance, but that adding
process don't change the root->processed_tlist's pointer address.
I checked "Assert(tlist == root->processed_tlist)" after calling query_planner
passes "make check".

9.
0003: line 1722-1763
In build_append_child_rel():

+	/*
+	 * In addition to the quals inherited from the parent, we might
+	 * have securityQuals associated with this particular child node.
+	 * (Currently this can only happen in appendrels originating from
+	 * UNION ALL; inheritance child tables don't have their own
+	 * securityQuals.)	Pull any such securityQuals up into the
...
+		foreach(lc, childRTE->securityQuals)
+		{
...
+		}
+		Assert(security_level <= root->qual_security_level);
+	}

This foreach loop loops only once in the current regression tests. I checked
"Assert(childRTE->securityQuals->length == 1)" passes "make check".
I think there are no need to change codes, I state this fact only for sharing.


--
Yoshikazu Imai 



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-12-06 08:25  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  0 siblings, 2 replies; 265+ messages in thread

From: Amit Langote @ 2018-12-06 08:25 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Imai-san,

Thanks for the review again.

On 2018/12/05 11:29, Imai, Yoshikazu wrote:
> On Tue, Nov 20, 2018 at 10:24 PM, Amit Langote wrote:
>> Attached v8 patches.
> 
> Thanks for the patch. I took a look 0003, 0005, 0006 of v8 patch.
> 
> 1.
> 0003: line 267-268
> +		 * Child relation may have be marked dummy if build_append_child_rel
> +		 * found self-contradictory quals.
> 
> /s/have be marked/have been marked/
> 
> 2.
> 0003: around line 1077
> In append.c(or prepunion.c)
> 228      * Check that there's at least one descendant, else treat as no-child
> 229      * case.  This could happen despite above has_subclass() check, if table
> 230      * once had a child but no longer does.
> 
> has_subclass() check is moved to subquery_planner from above this code,
> so the comments need to be modified like below.
> 
> s/above has_subclass() check/has_subclass() check in subquery_planner/
> 
> 3.
> 0003: line 1241-1244
> 0006: line ?
> 
> In comments of expand_partitioned_rtentry:
> + * Note: even though only the unpruned partitions will be added to the
> + * resulting plan, this still locks *all* partitions via find_all_inheritors
> + * in order to avoid partitions being locked in a different order than other
> + * places in the backend that may lock partitions.
> 
> This comments is no more needed if 0006 patch is applied because
> find_all_inheritors is removed in the 0006 patch.
> 
> 4.
> 0003: line 1541-1544
> 
> +	 * Add the RelOptInfo.  Even though we may not really scan this relation
> +	 * for reasons such as contradictory quals, we still need need to create
> +	 * one, because for every RTE in the query's range table, there must be an
> +	 * accompanying RelOptInfo.
> 
> s/need need/need/
> 
> 5.
> 0003: line 1620-1621
> 
> + * After creating the RelOptInfo for the given child RT index, it goes on to
> + * initialize some of its fields base on the parent RelOptInfo.
> 
> s/fields base on/fields based on/

Fixed all of 1-5.

> 6.
> parsenodes.h
>  906  *    inh is true for relation references that should be expanded to include
>  907  *    inheritance children, if the rel has any.  This *must* be false for
>  908  *    RTEs other than RTE_RELATION entries.
> 
> I think inh can become true now even if RTEKind equals RTE_SUBQUERY, so latter
> sentence need to be modified.

Seems like an existing comment bug.  Why don't you send a patch as you
discovered it? :)

> 7.
> 0005: line 109-115
> +		/*
> +		 * If partition is excluded by constraints, remove it from
> +		 * live_parts, too.
> +		 */
> +		if (IS_DUMMY_REL(childrel))
> +			parentrel->live_parts = bms_del_member(parentrel->live_parts, i);
> +
> 
> When I read this comment, I imagined that relation_excluded_by_constraints()
> would be called before this code. childrel is marked dummy if
> build_append_child_rel found self-contradictory quals, so comments can be
> modified more correctly like another comments in your patch as below.

I realized that bms_del_member statement is unnecessary.  I've revised the
comment describing live_parts to say that it contains indexes into
part_rels array of the non-NULL RelOptInfos contained in it, that is,
RelOptInfos of un-pruned partitions (rest of the entries are NULL.)
Un-pruned partitions may become dummy due to contradictory constraints or
constraint exclusion using normal CHECK constraints later and whether it's
dummy is checked properly by functions that iterate over live_parts.

> 8.
> 0003: line 705-711
> +		 * Query planning may have added some columns to the top-level tlist,
> +		 * which happens when there are row marks applied to inheritance
> +		 * parent relations (additional junk columns needed for applying row
> +		 * marks are added after expanding inheritance.)
> +		 */
> +		if (list_length(tlist) < list_length(root->processed_tlist))
> +			tlist = root->processed_tlist;
> 
> In grouping_planner():
> 
>   if (planned_rel == NULL)
>   {
>       ...
>       root->processed_tlist = tlist;
>   }
>   else
>       tlist = root->processed_tlist;
>   ...
>   if (current_rel == NULL)
>       current_rel = query_planner(root, tlist,
>                                   standard_qp_callback, &qp_extra);
>   ...
>   /*
>    * Query planning may have added some columns to the top-level tlist,
>    * which happens when there are row marks applied to inheritance
>    * parent relations (additional junk columns needed for applying row
>    * marks are added after expanding inheritance.)
>    */
>   if (list_length(tlist) < list_length(root->processed_tlist))
>       tlist = root->processed_tlist;
> 
> 
> Are there any case tlist points to an address different from
> root->processed_tlist after calling query_planner? Junk columns are possibly
> added to root->processed_tlist after expanding inheritance, but that adding
> process don't change the root->processed_tlist's pointer address.
> I checked "Assert(tlist == root->processed_tlist)" after calling query_planner
> passes "make check".

You're right.  I think that may not have been true in some version of the
patch that I didn't share on the list, but it is with the latest patch.
I've removed that block of code and adjusted nearby comments to mention
that targetlist may change during query_planner.

> 9.
> 0003: line 1722-1763
> In build_append_child_rel():
> 
> +	/*
> +	 * In addition to the quals inherited from the parent, we might
> +	 * have securityQuals associated with this particular child node.
> +	 * (Currently this can only happen in appendrels originating from
> +	 * UNION ALL; inheritance child tables don't have their own
> +	 * securityQuals.)	Pull any such securityQuals up into the
> ...
> +		foreach(lc, childRTE->securityQuals)
> +		{
> ...
> +		}
> +		Assert(security_level <= root->qual_security_level);
> +	}
> 
> This foreach loop loops only once in the current regression tests. I checked
> "Assert(childRTE->securityQuals->length == 1)" passes "make check".
> I think there are no need to change codes, I state this fact only for sharing.

Thanks for the information.  There aren't any changes to the code itself
due to this patch, just moved from one place to another.

Attached updated patches.  I have a few other changes in mind to make to
0001 such that the range table in each child's version of Query contains
only that child table in place of the original target relation, instead of
*all* child tables which is the current behavior.  The current behavior
makes range_table_mutator a big bottleneck when the number of un-pruned
target children is large.  But I'm saving it for the next week so that I
can prepare for the PGConf.ASIA that's starting on Monday next week.  See
you there. :)

Thanks,
Amit

Attachments: download all (6 files, .tar.gz)

v9-0001-Overhaul-inheritance-update-delete-planning.patch (text/plain, 56.6K) download From e2a65321f18ae22486e5a63b59233ef73c8e7eb1 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 11:18:12 +0900 Subject: [PATCH v9 1/6] Overhaul inheritance update/delete planning With the current method, inheritance_planner, which handles the planning for update/delete commands targeting inheritance trees, performs the *whole* planning for each child table. That involves translating the query tree to set a given child table in place of the original inheritance root table as the query's target table, followed by calling grouping_planner on the translated query, which performs query planning and additional steps needed to apply correct target list based on the child target table. That's inefficient for two reasons: 1. partprune.c cannot be used to perform partition pruning, because it can only be invoked from query_planner. With the current approach, query_planner only sees the individual partitions in the query tree it receives, not the parent partitioned table. This leaves each partition to be pruned using constraint exclusion. (constraint exclusion cannon prune hash partitions which means UPDATE/DELETE don't support hash partition pruning.) 2. Repeated invocation of query_planner results in allocating large amounts of memory, especially if there are many child tables. Also, it involves repeatedly performing the same processing, such as jointree processing. This commit addresses both of the issues by rewriting portions of inheritance_planner and adding some supporting functionality to allpath.c. With the new implementation, inheritance_planner calls query_planner only once at the beginning with the original unmodified query, which creates the access paths of individual relations after pruning any unnecessary partitions. For each target child relation, it also creates a sub-PartitionInfo containing translated version of the query and a targetlist suitable for the child. Child PlannerInfos are saved in the top PlannerInfo for using later. If the query involves join against the target relation, join paths are created for each target child relation by replacing the original target table in the join tree by a given child table. Join relations (RelOptInfos thereof) for all target child relations are collected in a global list in the top PlannerInfo. After creating the join paths for all target child relations, inheritance_planner calls grouping_planner() on each child join relation using the previously created child PlannerInfo to finish up the paths such that they produce query's top-level target list expanded according to a given child relation's descriptor. grouping_planner()'s interface is modified so that we can pass it what's called a 'planned_rel', a RelOptInfo that already contains the necessary paths needed to produce its output. This removes some existing code in inheritance_planner that dealt with any subquery RTEs in the query. The rationale of that code was that the subquery RTEs may change during each iteration of planning (that is, for different children), so different iterations better use different copies of those RTEs. That was handled by making fresh copies of those RTEs for each iteration of planning which were appended to the range table, accompanied by modifying all expressions and auxiliary structures that referenced the original subquery RTEs to instead reference the copies (that is, change the varnos). This copying meant we would end up adding S * N new entries to the original range table by the time we got to the last unpruned child, where S is the number of subquery RTEs in the original query and N the number of unpruned children. Since with the new code we perform planning just once, I think we don't need this special handling. Actually, there is a regression test output change due to no longer having copies of subquery RTEs (see the diff of partition_join.out file.) Also we no longer need the hack in relation_excluded_by_constraints() involving setting PlannerInfo.inhTargetKind to enable constraint exclusion for target child relation, which is no longer is no longer needed. Constraint exclusion runs during query_planner step described above. Regression test output change in partition_join.out is due to the fact that we no longer create duplicates of subquery RTEs in child range tables. --- doc/src/sgml/ddl.sgml | 14 +- src/backend/nodes/outfuncs.c | 1 - src/backend/optimizer/path/allpaths.c | 328 +++++++++++++++++++- src/backend/optimizer/path/equivclass.c | 27 +- src/backend/optimizer/plan/createplan.c | 10 - src/backend/optimizer/plan/planner.c | 432 +++++++-------------------- src/backend/optimizer/prep/prepjointree.c | 1 - src/backend/optimizer/prep/prepunion.c | 33 +- src/backend/optimizer/util/plancat.c | 50 +--- src/include/nodes/relation.h | 31 +- src/include/optimizer/paths.h | 3 +- src/test/regress/expected/partition_join.out | 4 +- 12 files changed, 517 insertions(+), 417 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 676a87aeb9..8974ce3654 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -4409,15 +4409,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <note> <para> - Currently, pruning of partitions during the planning of an - <command>UPDATE</command> or <command>DELETE</command> command is - implemented using the constraint exclusion method (however, it is - controlled by the <literal>enable_partition_pruning</literal> rather than - <literal>constraint_exclusion</literal>) &mdash; see the following section - for details and caveats that apply. - </para> - - <para> Execution-time partition pruning currently only occurs for the <literal>Append</literal> and <literal>MergeAppend</literal> node types. It is not yet implemented for the <literal>ModifyTable</literal> node @@ -4440,9 +4431,8 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <para> <firstterm>Constraint exclusion</firstterm> is a query optimization - technique similar to partition pruning. While it is primarily used - for partitioning implemented using the legacy inheritance method, it can be - used for other purposes, including with declarative partitioning. + technique similar to partition pruning. It is primarily used + for partitioning implemented using the legacy inheritance method. </para> <para> diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index f0c396530d..08657fcfd9 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2309,7 +2309,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); WRITE_UINT_FIELD(qual_security_level); - WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind); WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasDeletedRTEs); diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 738bb30848..4f7f034936 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -36,6 +36,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" @@ -93,6 +94,9 @@ static void set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); +static PlannerInfo *adjust_inherit_target_child(PlannerInfo *root, + RelOptInfo *childrel, + AppendRelInfo *appinfo); static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, @@ -119,6 +123,8 @@ static void set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); +static void inheritance_make_rel_from_joinlist(PlannerInfo *root, + List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, pushdown_safety_info *safetyInfo); static bool recurse_pushdown_safe(Node *setOp, Query *topquery, @@ -217,13 +223,40 @@ make_one_rel(PlannerInfo *root, List *joinlist) /* * Generate access paths for the entire join tree. + * + * For UPDATE/DELETE on an inheritance parent, join paths should be + * generated for each child result rel separately. */ - rel = make_rel_from_joinlist(root, joinlist); + if (root->parse->resultRelation && + root->simple_rte_array[root->parse->resultRelation]->inh) + { + inheritance_make_rel_from_joinlist(root, joinlist); - /* - * The result should join all and only the query's base rels. - */ - Assert(bms_equal(rel->relids, root->all_baserels)); + /* + * There should be as many child source rels as there are child + * subroots. + */ + Assert(list_length(root->inh_target_child_roots) == + list_length(root->inh_target_child_rels)); + + /* + * Return the RelOptInfo of original target relation, although this + * doesn't really contain the final path. inheritance_planner + * from where we got here will generate the final path, but not + * through this RelOptInfo. + */ + rel = find_base_rel(root, root->parse->resultRelation); + } + else + { + rel = make_rel_from_joinlist(root, joinlist); + + /* + * The result should join all and only the query's base rels. + */ + Assert(bms_equal(rel->relids, root->all_baserels)); + + } return rel; } @@ -1001,6 +1034,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, ListCell *parentvars; ListCell *childvars; ListCell *lc; + PlannerInfo *subroot = root; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1029,11 +1063,19 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, * fields of childrel->reltarget; not clear if that would be useful. */ childrel->reltarget->exprs = (List *) - adjust_appendrel_attrs(root, + adjust_appendrel_attrs(subroot, (Node *) rel->reltarget->exprs, 1, &appinfo); /* + * If the parent is the result relation, we need a reltarget for the + * child relation that will be suitable to use the child also as the + * target relation. + */ + if (appinfo->parent_relid == root->parse->resultRelation) + subroot = adjust_inherit_target_child(root, childrel, appinfo); + + /* * We have to make child entries in the EquivalenceClass data * structures as well. This is needed either if the parent * participates in some eclass joins (because we will want to consider @@ -1045,7 +1087,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, * EquivalenceClass data structures. */ if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) - add_child_rel_equivalences(root, appinfo, rel, childrel); + add_child_rel_equivalences(subroot, appinfo, rel, childrel, + root != subroot); childrel->has_eclass_joins = rel->has_eclass_joins; /* @@ -1188,7 +1231,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, /* CE failed, so finish copying/modifying join quals. */ childrel->joininfo = (List *) - adjust_appendrel_attrs(root, + adjust_appendrel_attrs(subroot, (Node *) rel->joininfo, 1, &appinfo); @@ -1216,12 +1259,12 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, * consistency, do this before calling set_rel_size() for the child. */ if (root->glob->parallelModeOK && rel->consider_parallel) - set_rel_consider_parallel(root, childrel, childRTE); + set_rel_consider_parallel(subroot, childrel, childRTE); /* - * Compute the child's size. + * Compute the child's size using possibly modified subroot. */ - set_rel_size(root, childrel, childRTindex, childRTE); + set_rel_size(subroot, childrel, childRTindex, childRTE); /* * It is possible that constraint exclusion detected a contradiction @@ -1231,6 +1274,25 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, if (IS_DUMMY_REL(childrel)) continue; + /* + * If we modified subroot for the target inheritance case, add it + * to root->inh_target_child_roots. + */ + if (subroot != root) + { + root->inh_target_child_roots = + lappend(root->inh_target_child_roots, subroot); + + /* + * If the childrel itself was a partitioned table, its children + * would've been added into subroot. + */ + if (subroot->inh_target_child_roots != NIL) + root->inh_target_child_roots = + list_concat(root->inh_target_child_roots, + subroot->inh_target_child_roots); + } + /* We have at least one live child. */ has_live_children = true; @@ -1327,6 +1389,125 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, } /* + * adjust_inherit_target_child + * For an inheritance target child relation, this adjusts its + * reltarget so that it contains expressions suitable for processing as + * a target relation, creates a child PlannerInfo containing translated + * copy of the query and returns it. + * + * The child PlannerInfo reuses most of the parent PlannerInfo's fields + * unchanged, except unexpanded_tlist and processed_tlist are based on the + * child relation. + */ +static PlannerInfo * +adjust_inherit_target_child(PlannerInfo *root, RelOptInfo *childrel, + AppendRelInfo *appinfo) +{ + PlannerInfo *subroot; + List *tlist; + List *translated_exprs; + ListCell *lc; + + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + + /* + * We'd like to build the reltarget afresh; save the translated + * version of parent's expressions aside. + */ + translated_exprs = childrel->reltarget->exprs; + childrel->reltarget->exprs = NIL; + + /* Translate the original query's expressions to this child. */ + subroot = makeNode(PlannerInfo); + memcpy(subroot, root, sizeof(PlannerInfo)); + + /* + * Restore the unexpanded tlist for translation, so that child's + * query contains targetList numbered (resnos) per its own + * TupleDesc, which adjust_inherited_tlist ensures. + */ + root->parse->targetList = root->unexpanded_tlist; + subroot->parse = (Query *) adjust_appendrel_attrs(root, + (Node *) root->parse, + 1, &appinfo); + + /* + * Save subroot's targetlist so that childrel's own children can use it as + * unexpanded tlist. Must copy because subroot->parse->targetList will + * be modified soon. + */ + subroot->unexpanded_tlist = list_copy(subroot->parse->targetList); + + /* + * Apply planner's expansion of targetlist, such as adding various junk + * column, filling placeholder entries for dropped columns, etc., all of + * which occurs with the child's TupleDesc. + */ + tlist = preprocess_targetlist(subroot); + subroot->processed_tlist = tlist; + build_base_rel_tlists(subroot, tlist); + + /* + * Some of the expressions in parent's reltarget might not be in the + * child's freshly built reltarget expressions, because the latter only + * contains those attributes that are needed to be present in the top- + * level tlist (or ones that preprocess_targetlist thinks are needed to + * be in the tlist.) We may need other attributes such as those that + * are required for computing WHERE clauses, which are already computed + * for the parent during deconstruct_jointree processing of the original + * query. We've already got a translated copy of those attributes, from + * which pick only those that are not already present. + */ + foreach(lc, translated_exprs) + { + Expr *expr = lfirst(lc); + + if (!list_member(childrel->reltarget->exprs, expr)) + childrel->reltarget->exprs = lappend(childrel->reltarget->exprs, + expr); + } + + /* + * Set a few other fields of subroot. + * + * Reset inh_target_child_roots to not be same as parent root's so that + * the subroots for this child's own children (if any) don't end up in + * root parent's list. We'll eventually merge all entries into one list, + * but that's now now. + */ + subroot->inh_target_child_roots = NIL; + + /* + * Adjust all_baserels to replace the original target relation with the + * child target relation. Copy it before modifying though. + */ + subroot->all_baserels = bms_copy(root->all_baserels); + subroot->all_baserels = bms_del_member(subroot->all_baserels, + root->parse->resultRelation); + subroot->all_baserels = bms_add_member(subroot->all_baserels, + subroot->parse->resultRelation); + + /* + * Child root should get its own copy of ECs, because they'll be modified + * to replace parent EC expressions by child expressions in + * add_child_rel_equivalences. + */ + subroot->eq_classes = NIL; + foreach(lc, root->eq_classes) + { + EquivalenceClass *ec = lfirst(lc); + EquivalenceClass *new_ec = makeNode(EquivalenceClass); + + memcpy(new_ec, ec, sizeof(EquivalenceClass)); + new_ec->ec_members = list_copy(ec->ec_members); + subroot->eq_classes = lappend(subroot->eq_classes, new_ec); + } + + return subroot; +} + +/* * set_append_rel_pathlist * Build access paths for an "append relation" */ @@ -2624,6 +2805,131 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows) } /* + * inheritance_make_rel_from_joinlist + * Perform join planning for all non-dummy leaf inheritance children + * in their role as an UPDATE/DELETE query's target relation + * + * If a child relation is a partitioned table, its children are processed in + * turn by recursively calling this function. + */ +static void +inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; +#ifdef USE_ASSERT_CHECKING + Relids all_baserels; +#endif + + /* + * For UPDATE/DELETE queries, the top parent can only ever be a table. + * As a contrast, it could be a UNION ALL subquery in the case of SELECT. + */ + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + Assert(planner_rt_fetch(resultRelation, root)->rtekind == RTE_RELATION); + + /* Nothing to do. */ + if (IS_DUMMY_REL(find_base_rel(root, resultRelation ))) + return; + + foreach(lc, root->inh_target_child_roots) + { + PlannerInfo *subroot = lfirst(lc); + RelOptInfo *childrel; + AppendRelInfo *appinfo; + List *translated_joinlist; + + Assert(subroot->parse->resultRelation > 0); + childrel = find_base_rel(root, subroot->parse->resultRelation); + appinfo = root->append_rel_array[subroot->parse->resultRelation]; + + if (appinfo->parent_relid != root->parse->resultRelation) + continue; + + /* Ignore excluded/pruned children. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* + * Modify joinlist such that relations joined to the top parent rel + * appear to be joined to the child rel instead. Do the same for + * any SpecialJoinInfo structs. + */ + translated_joinlist = (List *) + adjust_appendrel_attrs(subroot, + (Node *) joinlist, + 1, &appinfo); + subroot->join_info_list = (List *) + adjust_appendrel_attrs(subroot, + (Node *) root->join_info_list, + 1, &appinfo); + + /* + * Sub-partitioned tables have to be processed recursively using the + * translated subroot as the parent, because AppendRelInfos link + * sub-partitions to their immediate parents, not the root partitioned + * table. + */ + if (childrel->part_scheme != NULL) + { + inheritance_make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * Add this child relation as a placeholder in the parent root's + * inh_target_child_rels so that inheritance_planner see same + * number of entries as inh_target_child_roots. + */ + root->inh_target_child_rels = + lappend(root->inh_target_child_rels, childrel); + + /* Also propagate this child's own children into parent's list. */ + if (subroot->inh_target_child_rels != NIL) + root->inh_target_child_rels = + list_concat(root->inh_target_child_rels, + subroot->inh_target_child_rels); + continue; + } + + /* + * Since we added the child rel directly into the join tree, we must + * modify it to be a "base" rel instead of an "other" rel, which the + * join planning code expects the relations being joined to be. + */ + childrel->reloptkind = RELOPT_BASEREL; + + Assert(subroot->join_rel_list == NIL); + Assert(subroot->join_rel_hash == NULL); + + /* Perform join planning and save the resulting RelOptInfo. */ + childrel = make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * Remember this child target rel. inheritance_planner will perform + * the remaining steps of planning for each child relation separately. + * Specifically, it will call grouping_planner on every + * RelOptInfo contained in the inh_target_child_rels list, each of + * which represents the source of tuples to be modified for a given + * target child rel. + */ + root->inh_target_child_rels = + lappend(root->inh_target_child_rels, childrel); +#ifdef USE_ASSERT_CHECKING + /* + * The following implements essentially the same Assert as in + * make_one_rel, our caller. + */ + all_baserels = bms_copy(root->all_baserels); + all_baserels = bms_del_member(all_baserels, + root->parse->resultRelation); + all_baserels = bms_add_member(all_baserels, + subroot->parse->resultRelation); + Assert(bms_equal(childrel->relids, all_baserels)); +#endif + } +} + +/* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. * diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index b22b36ec0e..029665b974 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -2104,12 +2104,18 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root, * * parent_rel and child_rel could be derived from appinfo, but since the * caller has already computed them, we might as well just pass them in. + * + * If 'child_is_target' is true then the child EC members *replace* the + * corresponding parent members. In that case, 'root' is the child target + * relation's dedicated PlannerInfo so it makes sense to remove the parent + * ECs altogether, because they're of no use. */ void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, - RelOptInfo *child_rel) + RelOptInfo *child_rel, + bool child_is_target) { ListCell *lc1; @@ -2117,6 +2123,8 @@ add_child_rel_equivalences(PlannerInfo *root, { EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc1); ListCell *lc2; + ListCell *prev; + ListCell *next; /* * If this EC contains a volatile expression, then generating child @@ -2134,12 +2142,18 @@ add_child_rel_equivalences(PlannerInfo *root, !bms_is_subset(parent_rel->relids, cur_ec->ec_relids)) continue; - foreach(lc2, cur_ec->ec_members) + prev = NULL; + for (lc2 = list_head(cur_ec->ec_members); lc2 != NULL; lc2 = next) { EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2); + next = lnext(lc2); + if (cur_em->em_is_const) + { + prev = lc2; continue; /* ignore consts here */ + } /* Does it reference parent_rel? */ if (bms_overlap(cur_em->em_relids, parent_rel->relids)) @@ -2177,10 +2191,17 @@ add_child_rel_equivalences(PlannerInfo *root, child_rel->relids); } + /* Delete the parent EC member. */ + if (child_is_target) + cur_ec->ec_members = list_delete_cell(cur_ec->ec_members, + lc2, prev); + (void) add_eq_member(cur_ec, child_expr, new_relids, new_nullable_relids, - true, cur_em->em_datatype); + !child_is_target, cur_em->em_datatype); } + else + prev = lc2; } } } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index da7a92081a..28c4b53fea 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -1998,12 +1998,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) /* * During setrefs.c, we'll need the grouping_map to fix up the cols lists * in GroupingFunc nodes. Save it for setrefs.c to use. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be grouping in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->grouping_map == NULL); root->grouping_map = grouping_map; @@ -2160,12 +2155,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) * with InitPlan output params. (We can't just do that locally in the * MinMaxAgg node, because path nodes above here may have Agg references * as well.) Save the mmaggregates list to tell setrefs.c to do that. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be aggregates in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->minmax_aggs == NIL); root->minmax_aggs = best_path->mmaggregates; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index c729a99f8b..722a1f55ce 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -37,6 +37,7 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "nodes/relation.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" @@ -126,7 +127,7 @@ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static void inheritance_planner(PlannerInfo *root); static void grouping_planner(PlannerInfo *root, bool inheritance_update, - double tuple_fraction); + RelOptInfo *planned_rel, double tuple_fraction); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, int *tleref_to_colnum_map); @@ -630,7 +631,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->grouping_map = NULL; root->minmax_aggs = NIL; root->qual_security_level = 0; - root->inhTargetKind = INHKIND_NONE; root->hasRecursion = hasRecursion; if (hasRecursion) root->wt_param_id = SS_assign_special_param(root); @@ -970,7 +970,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh) inheritance_planner(root); else - grouping_planner(root, false, tuple_fraction); + grouping_planner(root, false, NULL, tuple_fraction); /* * Capture the set of outer-level param IDs we have access to, for use in @@ -1146,12 +1146,19 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr) * inheritance set. * * We have to handle this case differently from cases where a source relation - * is an inheritance set. Source inheritance is expanded at the bottom of the - * plan tree (see allpaths.c), but target inheritance has to be expanded at - * the top. The reason is that for UPDATE, each target relation needs a - * different targetlist matching its own column set. Fortunately, - * the UPDATE/DELETE target can never be the nullable side of an outer join, - * so it's OK to generate the plan this way. + * is an inheritance set. That's mainly because for UPDATE, each target + * relation needs a different targetlist matching its own column set. So, we + * must modify the source scan/join path for each target relation such that it + * produces the desired target list. + * + * The source scan/join paths for individual target relations are still + * created in allpaths.c by first expanding the inheritance in the usual way + * by set_append_rel_size, followed by join planning for each target relation + * separately in make_one_rel. Finally, we apply grouping_planner here to each + * child scan/join path so that it produces the desired targetlist. + * + * Fortunately, the UPDATE/DELETE target can never be the nullable side of an + * outer join, so it's OK to generate the plan this way. * * Returns nothing; the useful output is in the Paths we attach to * the (UPPERREL_FINAL, NULL) upperrel stored in *root. @@ -1164,14 +1171,8 @@ inheritance_planner(PlannerInfo *root) { Query *parse = root->parse; int top_parentRTindex = parse->resultRelation; - Bitmapset *subqueryRTindexes; - Bitmapset *modifiableARIindexes; int nominalRelation = -1; Index rootRelation = 0; - List *final_rtable = NIL; - int save_rel_array_size = 0; - RelOptInfo **save_rel_array = NULL; - AppendRelInfo **save_append_rel_array = NULL; List *subpaths = NIL; List *subroots = NIL; List *resultRelations = NIL; @@ -1179,70 +1180,59 @@ inheritance_planner(PlannerInfo *root) List *returningLists = NIL; List *rowMarks; RelOptInfo *final_rel; - ListCell *lc; - Index rti; + ListCell *lc1, + *lc2; RangeTblEntry *parent_rte; - PlannerInfo *parent_root; - Query *parent_parse; - Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); - PlannerInfo **parent_roots = NULL; + List *tlist; + standard_qp_extra qp_extra; + RelOptInfo *planned_rel; + /* Inheritance is never used for insert. */ Assert(parse->commandType != CMD_INSERT); + parent_rte = planner_rt_fetch(top_parentRTindex, root); /* - * We generate a modified instance of the original Query for each target - * relation, plan that, and put all the plans into a list that will be - * controlled by a single ModifyTable node. All the instances share the - * same rangetable, but each instance must have its own set of subquery - * RTEs within the finished rangetable because (1) they are likely to get - * scribbled on during planning, and (2) it's not inconceivable that - * subqueries could get planned differently in different cases. We need - * not create duplicate copies of other RTE kinds, in particular not the - * target relations, because they don't have either of those issues. Not - * having to duplicate the target relations is important because doing so - * (1) would result in a rangetable of length O(N^2) for N targets, with - * at least O(N^3) work expended here; and (2) would greatly complicate - * management of the rowMarks list. + * Generate the access paths for all relations mentioned in the query, + * including the target inheritance set. When doing the join planning, + * references in the join tree to the original target relation that's the + * root parent of the inheritance tree is replaced by each of its + * inheritance children and the resulting joinrel RelOptInfo's are + * added to root->inh_target_child_rels. * - * To begin with, generate a bitmapset of the relids of the subquery RTEs. + * Final planning steps (grouping_planner) are applied to the best path + * of each of those child joinrels using a modified instance of the + * original query for a given child target rel. All the paths so generated + * are put into a list that will be controlled by a single ModifyTable + * node. All the instances share the same rangetable. */ - subqueryRTindexes = NULL; - rti = 1; - foreach(lc, parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - if (rte->rtekind == RTE_SUBQUERY) - subqueryRTindexes = bms_add_member(subqueryRTindexes, rti); - rti++; - } /* - * Next, we want to identify which AppendRelInfo items contain references - * to any of the aforesaid subquery RTEs. These items will need to be - * copied and modified to adjust their subquery references; whereas the - * other ones need not be touched. It's worth being tense over this - * because we can usually avoid processing most of the AppendRelInfo - * items, thereby saving O(N^2) space and time when the target is a large - * inheritance tree. We can identify AppendRelInfo items by their - * child_relid, since that should be unique within the list. + * Save the unexpanded version of the query's targetlist to be used below + * for passing to grouping_planner for each child target relation. */ - modifiableARIindexes = NULL; - if (subqueryRTindexes != NULL) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); + root->unexpanded_tlist = list_copy(root->parse->targetList); - if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) || - bms_is_member(appinfo->child_relid, subqueryRTindexes) || - bms_overlap(pull_varnos((Node *) appinfo->translated_vars), - subqueryRTindexes)) - modifiableARIindexes = bms_add_member(modifiableARIindexes, - appinfo->child_relid); - } + /* Do the scan/join planning. */ + tlist = preprocess_targetlist(root); + root->processed_tlist = tlist; + qp_extra.tlist = tlist; + qp_extra.activeWindows = qp_extra.groupClause = NIL; + planned_rel = query_planner(root, tlist, standard_qp_callback, &qp_extra); + + /* + * If it turned out during query planning that all the children are dummy + * (pruned or excluded by constraints), no need to do the steps below. + * Let grouping_planner finish up the final path. + */ + if (IS_DUMMY_REL(planned_rel)) + { + grouping_planner(root, false, planned_rel, 0.0); + return; } + Assert(planned_rel->relid == top_parentRTindex); + Assert(planned_rel->reloptkind == RELOPT_BASEREL); + /* * If the parent RTE is a partitioned table, we should use that as the * nominal target relation, because the RTEs added for partitioned tables @@ -1250,7 +1240,6 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1258,95 +1247,40 @@ inheritance_planner(PlannerInfo *root) } /* - * The PlannerInfo for each child is obtained by translating the relevant - * members of the PlannerInfo for its immediate parent, which we find - * using the parent_relid in its AppendRelInfo. We save the PlannerInfo - * for each parent in an array indexed by relid for fast retrieval. Since - * the maximum number of parents is limited by the number of RTEs in the - * query, we use that number to allocate the array. An extra entry is - * needed since relids start from 1. + * Get on with finalizing the path for each child target relation by + * calling grouping_planner on its joinrel. Note that we're restoring + * the query's targetlist to the original one for grouping_planner's + * targetlist expansion steps to perform the expansion with individual + * child descriptors. */ - parent_roots = (PlannerInfo **) palloc0((list_length(parse->rtable) + 1) * - sizeof(PlannerInfo *)); - parent_roots[top_parentRTindex] = root; - - /* - * And now we can get on with generating a plan for each child table. - */ - foreach(lc, root->append_rel_list) + forboth(lc1, root->inh_target_child_roots, + lc2, root->inh_target_child_rels) { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - PlannerInfo *subroot; + PlannerInfo *subroot = lfirst(lc1); + RelOptInfo *childrel = lfirst(lc2); + AppendRelInfo *appinfo; RangeTblEntry *child_rte; RelOptInfo *sub_final_rel; Path *subpath; - /* append_rel_list contains all append rels; ignore others */ - if (!bms_is_member(appinfo->parent_relid, parent_relids)) - continue; - - /* - * expand_inherited_rtentry() always processes a parent before any of - * that parent's children, so the parent_root for this relation should - * already be available. - */ - parent_root = parent_roots[appinfo->parent_relid]; - Assert(parent_root != NULL); - parent_parse = parent_root->parse; - - /* - * We need a working copy of the PlannerInfo so that we can control - * propagation of information back to the main copy. - */ - subroot = makeNode(PlannerInfo); - memcpy(subroot, parent_root, sizeof(PlannerInfo)); - - /* - * Generate modified query with this rel as target. We first apply - * adjust_appendrel_attrs, which copies the Query and changes - * references to the parent RTE to refer to the current child RTE, - * then fool around with subquery RTEs. - */ - subroot->parse = (Query *) - adjust_appendrel_attrs(parent_root, - (Node *) parent_parse, - 1, &appinfo); + appinfo = root->append_rel_array[subroot->parse->resultRelation]; /* * If there are securityQuals attached to the parent, move them to the * child rel (they've already been transformed properly for that). */ - parent_rte = rt_fetch(appinfo->parent_relid, subroot->parse->rtable); - child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable); + parent_rte = planner_rt_fetch(appinfo->parent_relid, subroot); + child_rte = planner_rt_fetch(appinfo->child_relid, subroot); child_rte->securityQuals = parent_rte->securityQuals; parent_rte->securityQuals = NIL; /* - * Mark whether we're planning a query to a partitioned table or an - * inheritance parent. + * Ignore a partitioned child. Instead, the paths of its children will + * be added to subpaths. */ - subroot->inhTargetKind = - (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED; - - /* - * If this child is further partitioned, remember it as a parent. - * Since a partitioned table does not have any data, we don't need to - * create a plan for it, and we can stop processing it here. We do, - * however, need to remember its modified PlannerInfo for use when - * processing its children, since we'll update their varnos based on - * the delta from immediate parent to child, not from top to child. - * - * Note: a very non-obvious point is that we have not yet added - * duplicate subquery RTEs to the subroot's rtable. We mustn't, - * because then its children would have two sets of duplicates, - * confusing matters. - */ - if (child_rte->inh) + if (childrel->part_scheme) { - Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE); - parent_relids = bms_add_member(parent_relids, appinfo->child_relid); - parent_roots[appinfo->child_relid] = subroot; - + Assert(child_rte->inh); continue; } @@ -1374,111 +1308,8 @@ inheritance_planner(PlannerInfo *root) if (nominalRelation < 0) nominalRelation = appinfo->child_relid; - /* - * The rowMarks list might contain references to subquery RTEs, so - * make a copy that we can apply ChangeVarNodes to. (Fortunately, the - * executor doesn't need to see the modified copies --- we can just - * pass it the original rowMarks list.) - */ - subroot->rowMarks = copyObject(parent_root->rowMarks); - - /* - * The append_rel_list likewise might contain references to subquery - * RTEs (if any subqueries were flattenable UNION ALLs). So prepare - * to apply ChangeVarNodes to that, too. As explained above, we only - * want to copy items that actually contain such references; the rest - * can just get linked into the subroot's append_rel_list. - * - * If we know there are no such references, we can just use the outer - * append_rel_list unmodified. - */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - subroot->append_rel_list = NIL; - foreach(lc2, parent_root->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, modifiableARIindexes)) - appinfo2 = copyObject(appinfo2); - - subroot->append_rel_list = lappend(subroot->append_rel_list, - appinfo2); - } - } - - /* - * Add placeholders to the child Query's rangetable list to fill the - * RT indexes already reserved for subqueries in previous children. - * These won't be referenced, so there's no need to make them very - * valid-looking. - */ - while (list_length(subroot->parse->rtable) < list_length(final_rtable)) - subroot->parse->rtable = lappend(subroot->parse->rtable, - makeNode(RangeTblEntry)); - - /* - * If this isn't the first child Query, generate duplicates of all - * subquery RTEs, and adjust Var numbering to reference the - * duplicates. To simplify the loop logic, we scan the original rtable - * not the copy just made by adjust_appendrel_attrs; that should be OK - * since subquery RTEs couldn't contain any references to the target - * rel. - */ - if (final_rtable != NIL && subqueryRTindexes != NULL) - { - ListCell *lr; - - rti = 1; - foreach(lr, parent_parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lr); - - if (bms_is_member(rti, subqueryRTindexes)) - { - Index newrti; - - /* - * The RTE can't contain any references to its own RT - * index, except in its securityQuals, so we can save a - * few cycles by applying ChangeVarNodes to the rest of - * the rangetable before we append the RTE to it. - */ - newrti = list_length(subroot->parse->rtable) + 1; - ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); - ChangeVarNodes((Node *) subroot->rowMarks, rti, newrti, 0); - /* Skip processing unchanging parts of append_rel_list */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - foreach(lc2, subroot->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, - modifiableARIindexes)) - ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); - } - } - rte = copyObject(rte); - ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); - subroot->parse->rtable = lappend(subroot->parse->rtable, - rte); - } - rti++; - } - } - - /* There shouldn't be any OJ info to translate, as yet */ - Assert(subroot->join_info_list == NIL); - /* and we haven't created PlaceHolderInfos, either */ - Assert(subroot->placeholder_list == NIL); - - /* Generate Path(s) for accessing this result relation */ - grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ ); + /* Apply the top-level targetlist to childrel's path. */ + grouping_planner(subroot, true, childrel, 0.0); /* * Select cheapest path in case there's more than one. We always run @@ -1490,45 +1321,10 @@ inheritance_planner(PlannerInfo *root) subpath = sub_final_rel->cheapest_total_path; /* - * If this child rel was excluded by constraint exclusion, exclude it - * from the result plan. + * child rel cannot be empty, or inheritance_make_rel_from_joinlist + * wouldn't have put it in the list to begin with. */ - if (IS_DUMMY_PATH(subpath)) - continue; - - /* - * If this is the first non-excluded child, its post-planning rtable - * becomes the initial contents of final_rtable; otherwise, append - * just its modified subquery RTEs to final_rtable. - */ - if (final_rtable == NIL) - final_rtable = subroot->parse->rtable; - else - final_rtable = list_concat(final_rtable, - list_copy_tail(subroot->parse->rtable, - list_length(final_rtable))); - - /* - * We need to collect all the RelOptInfos from all child plans into - * the main PlannerInfo, since setrefs.c will need them. We use the - * last child's simple_rel_array (previous ones are too short), so we - * have to propagate forward the RelOptInfos that were already built - * in previous children. - */ - Assert(subroot->simple_rel_array_size >= save_rel_array_size); - for (rti = 1; rti < save_rel_array_size; rti++) - { - RelOptInfo *brel = save_rel_array[rti]; - - if (brel) - subroot->simple_rel_array[rti] = brel; - } - save_rel_array_size = subroot->simple_rel_array_size; - save_rel_array = subroot->simple_rel_array; - save_append_rel_array = subroot->append_rel_array; - - /* Make sure any initplans from this rel get into the outer list */ - root->init_plans = subroot->init_plans; + Assert(!IS_DUMMY_PATH(subpath)); /* Build list of sub-paths */ subpaths = lappend(subpaths, subpath); @@ -1560,36 +1356,6 @@ inheritance_planner(PlannerInfo *root) */ /* - * If we managed to exclude every child rel, return a dummy plan; it - * doesn't even need a ModifyTable node. - */ - if (subpaths == NIL) - { - set_dummy_rel_pathlist(final_rel); - return; - } - - /* - * Put back the final adjusted rtable into the master copy of the Query. - * (We mustn't do this if we found no non-excluded children.) - */ - parse->rtable = final_rtable; - root->simple_rel_array_size = save_rel_array_size; - root->simple_rel_array = save_rel_array; - root->append_rel_array = save_append_rel_array; - - /* Must reconstruct master's simple_rte_array, too */ - root->simple_rte_array = (RangeTblEntry **) - palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *)); - rti = 1; - foreach(lc, final_rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - root->simple_rte_array[rti++] = rte; - } - - /* * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will * have dealt with fetching non-locked marked rows, else we need to have * ModifyTable do that. @@ -1629,6 +1395,12 @@ inheritance_planner(PlannerInfo *root) * (inheritance_planner will create a single ModifyTable node covering all the * target tables.) * + * If non-NULL, planned_rel is a RelOptInfo containing paths for the query's + * top-level joinrel, which the caller produced by itself. In that case, this + * function only needs to adjust the targetlist of its cheapest_total_path. + * The only caller that may pass such a RelOptInfo currently is + * inheritance_planner. + * * tuple_fraction is the fraction of tuples we expect will be retrieved. * tuple_fraction is interpreted as follows: * 0: expect all tuples to be retrieved (normal case) @@ -1647,6 +1419,7 @@ inheritance_planner(PlannerInfo *root) */ static void grouping_planner(PlannerInfo *root, bool inheritance_update, + RelOptInfo *planned_rel, double tuple_fraction) { Query *parse = root->parse; @@ -1659,7 +1432,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, List *final_targets; List *final_targets_contain_srfs; bool final_target_parallel_safe; - RelOptInfo *current_rel; + RelOptInfo *current_rel = planned_rel; RelOptInfo *final_rel; ListCell *lc; @@ -1699,6 +1472,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * special work for recursive unions is the responsibility of * plan_set_operations. */ + Assert(current_rel == NULL); current_rel = plan_set_operations(root); /* @@ -1788,17 +1562,26 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, parse->groupClause = preprocess_groupclause(root, NIL); } - /* Preprocess targetlist */ - tlist = preprocess_targetlist(root); - /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation so - * that we can transfer its decoration (resnames etc) to the topmost - * tlist of the finished Plan. + * Preprocess targetlist, if needed. If the caller has already done + * query planning, root->processed_tlist already contains the desired + * targetlist. */ - root->processed_tlist = tlist; + if (planned_rel == NULL) + { + tlist = preprocess_targetlist(root); + + /* + * We are now done hacking up the query's targetlist. Most of the + * remaining planning work will be done with the PathTarget + * representation of tlists, but save aside the full representation + * so that we can transfer its decoration (resnames etc) to the + * topmost tlist of the finished Plan. + */ + root->processed_tlist = tlist; + } + else + tlist = root->processed_tlist; /* * Collect statistics about aggregates for estimating costs, and mark @@ -1878,8 +1661,9 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * We also generate (in standard_qp_callback) pathkey representations * of the query's sort clause, distinct clause, etc. */ - current_rel = query_planner(root, tlist, - standard_qp_callback, &qp_extra); + if (current_rel == NULL) + current_rel = query_planner(root, tlist, + standard_qp_callback, &qp_extra); /* * Convert the query's result tlist into PathTarget format. diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index cd6e11904e..78baec00dc 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -917,7 +917,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->grouping_map = NULL; subroot->minmax_aggs = NIL; subroot->qual_security_level = 0; - subroot->inhTargetKind = INHKIND_NONE; subroot->hasRecursion = false; subroot->wt_param_id = -1; subroot->non_recursive_path = NULL; diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 2a1c1cb2e1..134e7bafd9 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -2250,8 +2250,39 @@ adjust_appendrel_attrs_mutator(Node *node, context->appinfos); return (Node *) phv; } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index a570ac0aab..a0fed5be42 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1264,36 +1264,6 @@ get_relation_constraints(PlannerInfo *root, } } - /* - * Append partition predicates, if any. - * - * For selects, partition pruning uses the parent table's partition bound - * descriptor, instead of constraint exclusion which is driven by the - * individual partition's partition constraint. - */ - if (enable_partition_pruning && root->parse->commandType != CMD_SELECT) - { - List *pcqual = RelationGetPartitionQual(relation); - - if (pcqual) - { - /* - * Run the partition quals through const-simplification similar to - * check constraints. We skip canonicalize_qual, though, because - * partition quals should be in canonical form already; also, - * since the qual is in implicit-AND format, we'd have to - * explicitly convert it to explicit-AND format and back again. - */ - pcqual = (List *) eval_const_expressions(root, (Node *) pcqual); - - /* Fix Vars to have the desired varno */ - if (varno != 1) - ChangeVarNodes((Node *) pcqual, 1, varno, 0); - - result = list_concat(result, pcqual); - } - } - heap_close(relation, NoLock); return result; @@ -1420,31 +1390,15 @@ relation_excluded_by_constraints(PlannerInfo *root, switch (constraint_exclusion) { case CONSTRAINT_EXCLUSION_OFF: - - /* - * Don't prune if feature turned off -- except if the relation is - * a partition. While partprune.c-style partition pruning is not - * yet in use for all cases (update/delete is not handled), it - * would be a UI horror to use different user-visible controls - * depending on such a volatile implementation detail. Therefore, - * for partitioned tables we use enable_partition_pruning to - * control this behavior. - */ - if (root->inhTargetKind == INHKIND_PARTITIONED) - break; return false; case CONSTRAINT_EXCLUSION_PARTITION: /* * When constraint_exclusion is set to 'partition' we only handle - * OTHER_MEMBER_RELs, or BASERELs in cases where the result target - * is an inheritance parent or a partitioned table. + * OTHER_MEMBER_RELs. */ - if ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) && - !(rel->reloptkind == RELOPT_BASEREL && - root->inhTargetKind != INHKIND_NONE && - rel->relid == root->parse->resultRelation)) + if (rel->reloptkind != RELOPT_OTHER_MEMBER_REL) return false; break; diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 6fd24203dd..2ad3dc4711 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -319,9 +319,6 @@ typedef struct PlannerInfo Index qual_security_level; /* minimum security_level for quals */ /* Note: qual_security_level is zero if there are no securityQuals */ - InheritanceKind inhTargetKind; /* indicates if the target relation is an - * inheritance child or partition or a - * partitioned table */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */ bool hasDeletedRTEs; /* true if any RTE was deleted from jointree */ @@ -343,6 +340,34 @@ typedef struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* + * The following fields are set during query planning portion of an + * inherited UPDATE/DEELETE operation. + */ + + /* + * This stores the original version of the query's targetlist that's + * not modified by the planner. + */ + List *unexpanded_tlist; + + /* + * List containing a PlannerInfo corresponding to each child target rel. + * Content of each PlannerInfo is same as the parent PlannerInfo, except + * for the parse tree which is a translated copy of the parent's parse + * tree. + */ + List *inh_target_child_roots; + + /* + * RelOptInfos corresponding to each child target rel. For leaf children, + * it's the RelOptInfo representing the output of make_rel_from_joinlist() + * called with the parent rel in the original join tree replaced by a + * given leaf child. For non-leaf children, it's the baserel RelOptInfo + * itself, left as a placeholder. + */ + List *inh_target_child_rels; } PlannerInfo; diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index cafde307ad..7aa1b6e17f 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -161,7 +161,8 @@ extern EquivalenceClass *match_eclasses_to_foreign_key_col(PlannerInfo *root, extern void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, - RelOptInfo *child_rel); + RelOptInfo *child_rel, + bool child_is_target); extern List *generate_implied_equalities_for_column(PlannerInfo *root, RelOptInfo *rel, ec_matches_callback_type callback, diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index c55de5d476..c9f02c1fb0 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1780,7 +1780,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_1 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_1 -> Nested Loop Semi Join @@ -1788,7 +1788,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_2 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_2 (28 rows) -- 2.11.0
v9-0002-Store-inheritance-root-parent-index-in-otherrel-s.patch (text/plain, 2.5K) download From df5cab1f5347aa68731cb846f62635a7ce73e5c9 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 26 Oct 2018 16:45:59 +0900 Subject: [PATCH v9 2/6] Store inheritance root parent index in otherrel's RelOptInfo Although it's set by build_simple_rel, it's not being used by any code yet. --- src/backend/nodes/outfuncs.c | 1 + src/backend/optimizer/util/relnode.c | 14 ++++++++++++++ src/include/nodes/relation.h | 4 ++++ 3 files changed, 19 insertions(+) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 08657fcfd9..1d7c420e75 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2370,6 +2370,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_BOOL_FIELD(consider_partitionwise_join); WRITE_BITMAPSET_FIELD(top_parent_relids); WRITE_NODE_FIELD(partitioned_child_rels); + WRITE_UINT_FIELD(inh_root_parent); } static void diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 39f5729b91..29ba19349f 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -215,9 +215,23 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->top_parent_relids = parent->top_parent_relids; else rel->top_parent_relids = bms_copy(parent->relids); + + /* + * For inheritance child relations, we also set inh_root_parent. + * Note that 'parent' might itself be a child (a sub-partitioned + * partition), in which case we simply use its value of + * inh_root_parent. + */ + if (parent->rtekind == RTE_RELATION) + rel->inh_root_parent = parent->inh_root_parent > 0 ? + parent->inh_root_parent : + parent->relid; } else + { rel->top_parent_relids = NULL; + rel->inh_root_parent = 0; + } /* Check type of rtable entry */ switch (rte->rtekind) diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 2ad3dc4711..94f14019bd 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -728,6 +728,10 @@ typedef struct RelOptInfo List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ + + Index inh_root_parent; /* For otherrels, this is the RT index of + * inheritance table mentioned in the query + * from which this relation originated */ } RelOptInfo; /* -- 2.11.0
v9-0003-Lazy-creation-of-RTEs-for-inheritance-children.patch (text/plain, 85.2K) download From 70691ba07591d128851f37d8d58485c69bfb6643 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 18:14:13 +0900 Subject: [PATCH v9 3/6] Lazy creation of RTEs for inheritance children Currently, expand_inherited_tables called from subquery_planner opens all inheritance child tables and adds them to the query's range table and PlannerInfo in the form of AppendRelInfo's initially and later RelOptInfo's. For partitioning, that's pretty wasteful, because it's possible to determine only the partitions that will need to be scanned using partition pruning, which doesn't require opening the partitions themselves. However, the point at which the above processing happens is too early to perform partition pruning. This commit rearranges things within the planner so that inheritance child relations can be added to the Query and PlannerInfo during make_one_rel, just before set_base_rel_sizes is called. Although the late initialization approach only benefits the partitioning case, it seems undesirable to do it only for partitioned tables, because it means adding code to handle partitions specially at various places within the planner. So, *all* inheritance parent tables are expanded in make_one_rel. All unpruned partitions are added to Query in the form of their RangeTblEntry's being added to the range table and to PlannerInfo in the form of an AppendRelInfo and a RelOptInfo for each. For partitioning, although we don't create a RangeTblEntry and RelOptInfo for pruned partitions at make_one_rel time, partitionwise join code relies on the fact that even though partitions may have been pruned, they'd still own a RelOptInfo to handle the outer join case where the pruned partition appears on the nullable side of join. Partitionwise join code deals with that by allocating dummy RelOptInfos for pruned partitions that are based mostly on their parent's properties. There are some regression test diffs: 1. Caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child, as seen in the partition_aggregate.out test output. 2. Those in postgres_fdw.out are caused by the fact that junk columns required for row marking are added to reltarget->exprs later than user columns, because the row marking junk columns arent added until after the inheritance is expanded which as of this commit is later than it used to be. --- contrib/postgres_fdw/expected/postgres_fdw.out | 32 +- src/backend/optimizer/path/allpaths.c | 219 +----- src/backend/optimizer/path/joinrels.c | 62 ++ src/backend/optimizer/plan/initsplan.c | 58 -- src/backend/optimizer/plan/planner.c | 65 +- src/backend/optimizer/plan/setrefs.c | 6 + src/backend/optimizer/prep/preptlist.c | 15 +- src/backend/optimizer/prep/prepunion.c | 854 +++++++++++++++------- src/backend/optimizer/util/Makefile | 2 +- src/backend/optimizer/util/plancat.c | 40 +- src/backend/optimizer/util/relnode.c | 80 +- src/backend/partitioning/partprune.c | 43 +- src/include/nodes/relation.h | 8 + src/include/optimizer/pathnode.h | 1 + src/include/optimizer/plancat.h | 2 +- src/include/optimizer/prep.h | 9 +- src/include/partitioning/partprune.h | 2 +- src/test/regress/expected/partition_aggregate.out | 4 +- 18 files changed, 883 insertions(+), 619 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index dfa6201b3f..44d12a27b8 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7064,15 +7064,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7102,15 +7102,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7139,15 +7139,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid @@ -7157,15 +7157,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (39 rows) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 4f7f034936..3eae8316d5 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -44,7 +44,6 @@ #include "optimizer/var.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -180,6 +179,16 @@ make_one_rel(PlannerInfo *root, List *joinlist) set_base_rel_consider_startup(root); /* + * Expand RT entries that represent inherited or partitioned tables. + * This will perform partition pruning on partitioned tables in the + * original range table and also recursively on any child partitioned + * tables that were added by the expansion of the original parent(s). + * As new entries are added to the range table, various arrays in the + * PlannerInfo will be expanded accordingly. + */ + expand_inherited_tables(root); + + /* * Compute size estimates and consider_parallel flags for each base rel. */ set_base_rel_sizes(root); @@ -404,7 +413,11 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, */ set_dummy_rel_pathlist(rel); } - else if (rte->inh) + /* + * expand_inherited_tables may have proved that the relation is empty, so + * check if it's so. + */ + else if (rte->inh && !IS_DUMMY_REL(rel)) { /* It's an "append relation", process accordingly */ set_append_rel_size(root, rel, rti, rte); @@ -957,8 +970,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -966,32 +977,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Assert(IS_SIMPLE_REL(rel)); /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, we will bubble up - * the indexes of partitioned relations that appear down in the tree, so - * that when we've created Paths for all the children, the root - * partitioned table's list will contain all such indexes. - */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - rel->partitioned_child_rels = list_make1_int(rti); - - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - - /* * If this is a partitioned baserel, set the consider_partitionwise_join * flag; currently, we only consider partitionwise joins with the baserel * if its targetlist doesn't contain a whole-row Var. @@ -1028,12 +1013,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; ListCell *parentvars; ListCell *childvars; - ListCell *lc; PlannerInfo *subroot = root; /* append_rel_list contains all append rels; ignore others */ @@ -1042,18 +1023,34 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo is created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); /* - * Copy/Modify targetlist. Even if this child is deemed empty, we need - * its targetlist in case it falls on nullable side in a child-join - * because of partitionwise join. + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals. + */ + if (IS_DUMMY_REL(childrel)) + continue; + + if (relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* + * This child need not be scanned, so we can omit it from the + * appendrel. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Copy/Modify targetlist. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -1091,144 +1088,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, root != subroot); childrel->has_eclass_joins = rel->has_eclass_joins; - /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* - * This child need not be scanned, so we can omit it from the - * appendrel. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - /* CE failed, so finish copying/modifying join quals. */ childrel->joininfo = (List *) adjust_appendrel_attrs(subroot, @@ -1442,9 +1301,10 @@ adjust_inherit_target_child(PlannerInfo *root, RelOptInfo *childrel, /* * Apply planner's expansion of targetlist, such as adding various junk * column, filling placeholder entries for dropped columns, etc., all of - * which occurs with the child's TupleDesc. + * which occurs with the child's TupleDesc. Since inheritance has already + * been expanded (we wouldn't be here otherwise), pass true. */ - tlist = preprocess_targetlist(subroot); + tlist = preprocess_targetlist(subroot, true); subroot->processed_tlist = tlist; build_base_rel_tlists(subroot, tlist); @@ -2842,6 +2702,7 @@ inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) Assert(subroot->parse->resultRelation > 0); childrel = find_base_rel(root, subroot->parse->resultRelation); + Assert(childrel != NULL); appinfo = root->append_rel_array[subroot->parse->resultRelation]; if (appinfo->parent_relid != root->parse->resultRelation) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index d3d21fed5d..da0831de4e 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -20,6 +20,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/prep.h" +#include "optimizer/tlist.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -46,6 +47,9 @@ static void try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, List *parent_restrictlist); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + int partidx); /* @@ -1376,6 +1380,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts); + if (child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts); + /* We should never try to join two overlapping sets of rels. */ Assert(!bms_overlap(child_rel1->relids, child_rel2->relids)); child_joinrelids = bms_union(child_rel1->relids, child_rel2->relids); @@ -1582,3 +1591,56 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx) +{ + RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + /* Pass parent's info as for both the parent rel and child rel. */ + AppendRelInfo *appinfo = make_append_rel_info(parent, parentrte, + parent->tupdesc, + parentrte->relid, + parent->reltype, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 01335db511..7636aa82c4 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -616,64 +616,6 @@ create_lateral_join_info(PlannerInfo *root) bms_add_member(brel2->lateral_referencers, rti); } } - - /* - * Lastly, propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - */ - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - RangeTblEntry *brte = root->simple_rte_array[rti]; - - /* - * Skip empty slots. Also skip non-simple relations i.e. dead - * relations. - */ - if (brel == NULL || !IS_SIMPLE_REL(brel)) - continue; - - /* - * In the case of table inheritance, the parent RTE is directly linked - * to every child table via an AppendRelInfo. In the case of table - * partitioning, the inheritance hierarchy is expanded one level at a - * time rather than flattened. Therefore, an other member rel that is - * a partitioned table may have children of its own, and must - * therefore be marked with the appropriate lateral info so that those - * children eventually get marked also. - */ - Assert(brte); - if (brel->reloptkind == RELOPT_OTHER_MEMBER_REL && - (brte->rtekind != RTE_RELATION || - brte->relkind != RELKIND_PARTITIONED_TABLE)) - continue; - - if (brte->inh) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); - RelOptInfo *childrel; - - if (appinfo->parent_relid != rti) - continue; - childrel = root->simple_rel_array[appinfo->child_relid]; - Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - Assert(childrel->direct_lateral_relids == NULL); - childrel->direct_lateral_relids = brel->direct_lateral_relids; - Assert(childrel->lateral_relids == NULL); - childrel->lateral_relids = brel->lateral_relids; - Assert(childrel->lateral_referencers == NULL); - childrel->lateral_referencers = brel->lateral_referencers; - } - } - } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 722a1f55ce..1ee7d9585b 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -23,6 +23,7 @@ #include "access/sysattr.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -699,27 +700,24 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } /* * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). + * pullup (so that all non-inherited RTEs are present). */ preprocess_rowmarks(root); /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain base relations not joins, so it's OK (and marginally more - * efficient) to do it after checking for join RTEs. We must do it after - * pulling up subqueries, else we'd fail to handle inherited tables in - * subqueries. - */ - expand_inherited_tables(root); - - /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -1212,8 +1210,11 @@ inheritance_planner(PlannerInfo *root) */ root->unexpanded_tlist = list_copy(root->parse->targetList); - /* Do the scan/join planning. */ - tlist = preprocess_targetlist(root); + /* + * Do the scan/join planning. We haven't expanded inheritance yet, so + * pass false. + */ + tlist = preprocess_targetlist(root, false); root->processed_tlist = tlist; qp_extra.tlist = tlist; qp_extra.activeWindows = qp_extra.groupClause = NIL; @@ -1222,9 +1223,10 @@ inheritance_planner(PlannerInfo *root) /* * If it turned out during query planning that all the children are dummy * (pruned or excluded by constraints), no need to do the steps below. - * Let grouping_planner finish up the final path. + * For regular inheritance case, it's possible that we only need to modify + * the parent table. Let grouping_planner finish up the final path. */ - if (IS_DUMMY_REL(planned_rel)) + if (!parent_rte->inh || IS_DUMMY_REL(planned_rel)) { grouping_planner(root, false, planned_rel, 0.0); return; @@ -1569,14 +1571,19 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, */ if (planned_rel == NULL) { - tlist = preprocess_targetlist(root); + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root, false); /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation - * so that we can transfer its decoration (resnames etc) to the - * topmost tlist of the finished Plan. + * We are now mostly done hacking up the query's targetlist. More + * columns might be added during query_planner after inheritance + * expansion in make_one_rel, because some children may require + * different column than the parent for row mark handling; see + * add_rowmark_junk_columns(). Most of the remaining planning + * work will be done with the PathTarget representation of tlists, + * but save aside the full representation so that we can transfer + * its decoration (resnames etc) to the topmost tlist of the + * finished Plan. */ root->processed_tlist = tlist; } @@ -2352,7 +2359,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2377,7 +2384,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } @@ -6757,6 +6764,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -6858,6 +6869,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 6d6ef1c376..454870609a 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -326,6 +326,12 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) UPPERREL_FINAL, NULL))) add_rtes_to_flat_rtable(rel->subroot, true); } + /* + * A NULL rel also means an unplanned subquery rte, so apply + * flatten_unplanned_rtes. + */ + else + flatten_unplanned_rtes(glob, rte); } rti++; } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 8603feef2b..0747403acd 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -65,9 +65,13 @@ static List *expand_targetlist(List *tlist, int command_type, * * As a side effect, if there's an ON CONFLICT UPDATE clause, its targetlist * is also preprocessed (and updated in-place). + * + * inheritance_expanded specifies whether inheritance tables are expanded + * as of calling this function. Caller must specify it because that affects + * which row marking related junk columns get added to the targetlist. */ List * -preprocess_targetlist(PlannerInfo *root) +preprocess_targetlist(PlannerInfo *root, bool inheritance_expanded) { Query *parse = root->parse; int result_relation = parse->resultRelation; @@ -134,6 +138,15 @@ preprocess_targetlist(PlannerInfo *root) if (rc->rti != rc->prti) continue; + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which will change the parent row + * mark's allMarkTypes fields. + */ + if (rc->isParent && !inheritance_expanded) + continue; + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) { /* Need to fetch TID */ diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 134e7bafd9..96430a4cc7 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -39,15 +39,19 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" +#include "optimizer/var.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" +#include "partitioning/partprune.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/selfuncs.h" @@ -99,23 +103,26 @@ static List *generate_append_tlist(List *colTypes, List *colCollations, List *input_tlists, List *refnames_tlist); static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist); +static void expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti); static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); + Index rti, RelOptInfo *rel); static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); -static void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p); +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); +static RelOptInfo *build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, @@ -1472,33 +1479,134 @@ generate_setop_grouplist(SetOperationStmt *op, List *targetlist) void expand_inherited_tables(PlannerInfo *root) { - Index nrtes; + int orig_rtable_size; Index rti; - ListCell *rl; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; /* - * expand_inherited_rtentry may add RTEs to parse->rtable. The function is + * expand_append_rtentry may add RTEs to parse->rtable. The function is * expected to recursively handle any RTEs that it creates with inh=true. * So just scan as far as the original end of the rtable list. */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) + for (rti = 1; rti < orig_rtable_size; rti++) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + if (rte->inh) + expand_append_rtentry(root, brel, rte, rti); + } +} + +/* + * expand_append_rtentry + * This initializes RelOptInfos for an appendrel's child relations, if + * any + * + * 'rel' is the appendrel parent, whose range table entry ('rte') has been + * marked to require adding children. An appendrel parent could either + * be a subquery (if we flattened UNION ALL query) or a table that's known + * to have inheritance children. The latter consists of both regular + * inheritance parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as an appendrel child. The original RTE is + * considered to represent the whole inheritance set. + */ +static void +expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, + Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_append_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_inherited_rtentry(root, rte, rti, rel); } } /* * expand_inherited_rtentry - * Check whether a rangetable entry represents an inheritance set. - * If so, add entries for all the child tables to the query's - * rangetable, and build AppendRelInfo nodes for all the child tables - * and add them to root->append_rel_list. If not, clear the entry's - * "inh" flag to prevent later code from looking for AppendRelInfos. + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. * * Note that the original RTE is considered to represent the whole * inheritance set. The first of the generated RTEs is an RTE for the same @@ -1509,55 +1617,33 @@ expand_inherited_tables(PlannerInfo *root) * regular inheritance, a parent RTE must always have at least two associated * AppendRelInfos: one corresponding to the parent table as a simple member of * inheritance set and one or more corresponding to the actual children. - * Since a partitioned table is not scanned, it might have only one associated - * AppendRelInfo. */ static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, + RelOptInfo *rel) { Oid parentOID; PlanRowMark *oldrc; - Relation oldrelation; - LOCKMODE lockmode; + LOCKMODE lockmode = rte->rellockmode; List *inhOIDs; ListCell *l; + int num_children; + int num_children_added = 0; - /* Does RT entry allow inheritance? */ - if (!rte->inh) - return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } - /* Fast path for common case of childless table */ + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); parentOID = rte->relid; - if (!has_subclass(parentOID)) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * The rewriter should already have obtained an appropriate lock on each - * relation named in the query. However, for each child relation we add - * to the query, we must obtain an appropriate lock, because this will be - * the first use of those relations in the parse/rewrite/plan pipeline. - * Child rels should use the same lockmode as their parent. - */ - lockmode = rte->rellockmode; /* Scan for all members of inheritance set, acquire needed locks */ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); /* * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite above has_subclass() check, if table - * once had a child but no longer does. + * case. This could happen despite has_subclass() check performed by + * subquery_planner, if table once had a child but no longer does. */ - if (list_length(inhOIDs) < 2) + num_children = list_length(inhOIDs); + if (num_children < 2) { /* Clear flag before returning */ rte->inh = false; @@ -1565,216 +1651,219 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } /* - * If parent relation is selected FOR UPDATE/SHARE, we need to mark its - * PlanRowMark as isParent = true, and generate a new PlanRowMark for each - * child. + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. */ oldrc = get_plan_rowmark(root->rowMarks, rti); - if (oldrc) - oldrc->isParent = true; + Assert(oldrc == NULL || oldrc->isParent); /* - * Must open the parent relation to examine its tupdesc. We need not lock - * it; we assume the rewriter already did. + * Must expand PlannerInfo arrays by num_children before we can add + * children. */ - oldrelation = heap_open(parentOID, NoLock); + expand_planner_arrays(root, num_children); - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) + foreach(l, inhOIDs) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); - - /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. - */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); - } - else - { - List *appinfos = NIL; + Oid childOID = lfirst_oid(l); + Relation newrelation; RangeTblEntry *childrte; Index childRTindex; + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. */ - foreach(l, inhOIDs) + if (RELATION_IS_OTHER_TEMP(newrelation)) { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = heap_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - heap_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - heap_close(newrelation, NoLock); + heap_close(newrelation, lockmode); + continue; } - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); + (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation, &childrte, + &childRTindex); + Assert(childrte != NULL); + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + Assert(childRTindex > 0); + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; } - heap_close(oldrelation, NoLock); + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } } /* * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) + Index parentRTindex, RelOptInfo *parentrel) { + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; - check_stack_depth(); - - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + expand_planner_arrays(root, bms_num_members(partindexes)); /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. */ - if (partdesc->nparts == 0) - { - parentrte->inh = false; - return; - } + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); - for (i = 0; i < partdesc->nparts; i++) + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) { - Oid childOID = partdesc->oids[i]; - Relation childrel; + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + RangeTblEntry *childrte; + Index childRTindex; - /* Open rel; we already have required locks */ - childrel = heap_open(childOID, NoLock); + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation, + &childrte, &childRTindex); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + + /* If the child is partitioned itself, expand it too. */ + if (childrel->part_scheme) + { + Assert(childrte->inh); expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); + childrel); + } + } - /* Close child relation, but keep locks */ - heap_close(childrel, NoLock); + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); } } /* - * expand_single_inheritance_child - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) + * The return value is the RelOptInfo that's added. * * PlanRowMarks still carry the top-parent's RTI, and the top-parent's * allMarkTypes field still accumulates values from all descendents. * * "parentrte" and "parentRTindex" are immediate parent's RTE and * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. */ -static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); Oid childOID = RelationGetRelid(childrel); RangeTblEntry *childrte; Index childRTindex; AppendRelInfo *appinfo; + RelOptInfo *childrelopt; /* * Build an RTE for the child, and attach to query's rangetable list. We * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh = false. Also, set requiredPerms to zero since - * all required permissions checks are done on the original RTE. Likewise, - * set the child's securityQuals to empty, because we only want to apply - * the parent's RLS conditions regardless of what RLS properties + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties * individual children may have. (This is an intentional choice to make * inherited RLS work like regular permissions checks.) The parent * securityQuals will be propagated to children along with other base @@ -1784,55 +1873,42 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childrte_p = childrte; childrte->relid = childOID; childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) - childrte->inh = true; - else - childrte->inh = false; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); childrte->requiredPerms = 0; childrte->securityQuals = NIL; parse->rtable = lappend(parse->rtable, childrte); childRTindex = list_length(parse->rtable); *childRTindex_p = childRTindex; - /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. - */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) - { - appinfo = makeNode(AppendRelInfo); - appinfo->parent_relid = parentRTindex; - appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->rd_rel->reltype; - appinfo->child_reltype = childrel->rd_rel->reltype; - make_inh_translation_list(parentrel, childrel, childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = parentOID; - *appinfos = lappend(*appinfos, appinfo); + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + childrel->rd_att, + childOID, + childrel->rd_rel->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); } /* @@ -1864,6 +1940,24 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need to create one, + * because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_append_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; } /* @@ -1874,14 +1968,11 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, * For paranoia's sake, we match type/collation as well as attribute name. */ static void -make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, - List **translated_vars) +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) { List *vars = NIL; - TupleDesc old_tupdesc = RelationGetDescr(oldrelation); - TupleDesc new_tupdesc = RelationGetDescr(newrelation); - Oid new_relid = RelationGetRelid(newrelation); int oldnatts = old_tupdesc->natts; int newnatts = new_tupdesc->natts; int old_attno; @@ -1911,7 +2002,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, * When we are generating the "translation list" for the parent table * of an inheritance set, no need to search for matches. */ - if (oldrelation == newrelation) + if (from_rel == to_rel) { vars = lappend(vars, makeVar(newvarno, (AttrNumber) (old_attno + 1), @@ -1937,10 +2028,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, { HeapTuple newtup; - newtup = SearchSysCacheAttName(new_relid, attname); + newtup = SearchSysCacheAttName(to_rel, attname); if (!newtup) elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; ReleaseSysCache(newtup); @@ -1950,10 +2041,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, /* Found it, check type and collation match */ if (atttypid != att->atttypid || atttypmod != att->atttypmod) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); if (attcollation != att->attcollation) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); vars = lappend(vars, makeVar(newvarno, (AttrNumber) (new_attno + 1), @@ -2020,6 +2111,255 @@ translate_col_privs(const Bitmapset *parent_privs, } /* + * build_append_child_rel + * Build a RelOptInfo for child relation of an append rel + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields based on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turn out to be self-contradictory, + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + List *childquals; + ListCell *lc; + bool have_const_false_cq; + Index cq_min_security; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = false; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, parent->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual = (Node *) rinfo->clause; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, childqual, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might + * have securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals.) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except + * that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* + * not likely that we'd see constants here, so no + * check + */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, + false, + false, + security_level, + NULL, NULL, + NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* Set child's version of baserestrictinfo. */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; +} + +AppendRelInfo * +make_append_rel_info(RelOptInfo *parentrel, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex) +{ + AppendRelInfo *appinfo = makeNode(AppendRelInfo); + + appinfo->parent_relid = parentrel->relid; + appinfo->child_relid = childRTindex; + appinfo->parent_reltype = parentrel->reltype; + appinfo->child_reltype = childreltype; + make_inh_translation_list(parentrel->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, + &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; + + return appinfo; +} + +/* * adjust_appendrel_attrs * Copy the specified query or expression and translate Vars referring to a * parent rel to refer to the corresponding child rel instead. We also diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile index c54d0a690d..da9ccf32b4 100644 --- a/src/backend/optimizer/util/Makefile +++ b/src/backend/optimizer/util/Makefile @@ -13,6 +13,6 @@ top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global OBJS = clauses.o joininfo.o orclauses.o pathnode.o placeholder.o \ - plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o + plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index a0fed5be42..bf441741e0 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -106,7 +106,7 @@ static void set_baserel_partition_key_exprs(Relation relation, */ void get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) + Bitmapset *updatedCols, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; @@ -444,11 +444,31 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, get_relation_foreign_keys(root, rel, relation, inhparent); /* - * Collect info about relation's partitioning scheme, if any. Only - * inheritance parents may be partitioned. + * Collect some additional information for inheritance parents. */ - if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - set_relation_partition_info(root, rel, relation); + if (inhparent) + { + /* + * We'll need the TupleDesc when initializing the child relation. + * A copy is being made because concurrent changes might drop + * the relcache entry. That's possible because ALTER TABLE + * child_table NO INHERIT parent_table only requires an + * AccessShareLock on parent_table. + */ + rel->tupdesc = CreateTupleDescCopy(RelationGetDescr(relation)); + rel->reltype = RelationGetForm(relation)->reltype; + + /* + * If partitioned, also save the information of partitioning scheme, + * and whether the query updates any of the partition key columns. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + set_relation_partition_info(root, rel, relation); + root->partColsUpdated |= has_partition_attrs(relation, updatedCols, + NULL); + } + } heap_close(relation, NoLock); @@ -1853,16 +1873,20 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; + + /* + * Since we must've taken a lock on the table, it's okay to simply copy + * the pointers to relcache data here. + */ + rel->part_oids = partdesc->oids; + rel->boundinfo = partdesc->boundinfo; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 29ba19349f..c23db9d78d 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -130,6 +130,37 @@ setup_append_rel_array(PlannerInfo *root) } } +/* Expand the PlannerInfo arrays by add_size members and zero-init it. */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size = root->simple_rel_array_size + add_size; + + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + if (root->append_rel_array) + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + else + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + + /* Set the contents of just allocated memory to 0. */ + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + root->simple_rel_array_size = new_size; +} + /* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. @@ -238,7 +269,8 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte->relid, rte->inh, rte->updatedCols, + rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -279,52 +311,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); - /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. - */ - if (rte->inh) - { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); - - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; - } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); - } - return rel; } diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 35c87535d3..fcf8d6032c 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -45,6 +45,7 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/planner.h" #include "optimizer/predtest.h" @@ -436,17 +437,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -540,23 +547,20 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ @@ -564,6 +568,13 @@ prune_append_rel_partitions(RelOptInfo *rel) return NULL; /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. */ @@ -590,15 +601,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 94f14019bd..e6fdbcd030 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -15,6 +15,7 @@ #define RELATION_H #include "access/sdir.h" +#include "access/tupdesc.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/params.h" @@ -723,6 +724,7 @@ typedef struct RelOptInfo int nparts; /* number of partitions */ struct PartitionBoundInfoData *boundinfo; /* Partition bounds */ List *partition_qual; /* partition constraint */ + Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ List **partexprs; /* Non-nullable partition key expressions. */ @@ -732,6 +734,12 @@ typedef struct RelOptInfo Index inh_root_parent; /* For otherrels, this is the RT index of * inheritance table mentioned in the query * from which this relation originated */ + /* + * Set only if this is an inheritance parent relation. This information + * is needed when initializing the planning info for children. + */ + TupleDesc tupdesc; /* A "copy" of the table's tuple desriptor */ + Oid reltype; /* Table's reltype */ } RelOptInfo; /* diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 81abcf53a8..b1baa3117a 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -261,6 +261,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index 7d53cbbb87..edaf2a3b4f 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -26,7 +26,7 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); + bool inhparent, Bitmapset *updatedCols, RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 38608770a2..faae07d240 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -38,7 +38,8 @@ extern Expr *canonicalize_qual(Expr *qual, bool is_check); /* * prototypes for preptlist.c */ -extern List *preprocess_targetlist(PlannerInfo *root); +extern List *preprocess_targetlist(PlannerInfo *root, + bool inheritance_expanded); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); @@ -51,7 +52,10 @@ extern void expand_inherited_tables(PlannerInfo *root); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); - +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parentrel, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex); extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, Relids child_relids, Relids top_parent_relids); @@ -64,5 +68,4 @@ extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, Relids left_relids, Relids right_relids); extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, Relids child_relids, Relids top_parent_relids); - #endif /* PREP_H */ diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h index e07aaaf798..ac0a979010 100644 --- a/src/include/partitioning/partprune.h +++ b/src/include/partitioning/partprune.h @@ -76,7 +76,7 @@ extern PartitionPruneInfo *make_partition_pruneinfo(PlannerInfo *root, List *subpaths, List *partitioned_rels, List *prunequal); -extern Relids prune_append_rel_partitions(RelOptInfo *rel); +extern Bitmapset *prune_append_rel_partitions(RelOptInfo *rel); extern Bitmapset *get_matching_partitions(PartitionPruneContext *context, List *pruning_steps); diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 6bc106831e..1450cef057 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) -- 2.11.0
v9-0004-Move-append-expansion-code-into-its-own-file.patch (text/plain, 112.2K) download From 929ab0e596a5103ee58f969c8b5220267c41a5d1 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 7 Nov 2018 16:51:31 +0900 Subject: [PATCH v9 4/6] Move append expansion code into its own file This commit moves expand_append_rel and underlings currently in optimizer/prep/prepunionc.c to optimizer/utils/append.c. All of the AppendRelInfo based expression manipulation routines are moved to optimizer/utils/appendinfo.c. This commit only moves the code and contains no functional changes. --- src/backend/optimizer/path/allpaths.c | 2 + src/backend/optimizer/path/equivclass.c | 1 + src/backend/optimizer/path/joinrels.c | 1 + src/backend/optimizer/plan/planner.c | 1 + src/backend/optimizer/prep/prepunion.c | 1573 ------------------------------- src/backend/optimizer/util/Makefile | 5 +- src/backend/optimizer/util/append.c | 776 +++++++++++++++ src/backend/optimizer/util/appendinfo.c | 851 +++++++++++++++++ src/backend/optimizer/util/pathnode.c | 1 + src/backend/optimizer/util/relnode.c | 1 + src/backend/partitioning/partprune.c | 1 + src/include/optimizer/append.h | 25 + src/include/optimizer/appendinfo.h | 43 + src/include/optimizer/prep.h | 19 - 14 files changed, 1706 insertions(+), 1594 deletions(-) create mode 100644 src/backend/optimizer/util/append.c create mode 100644 src/backend/optimizer/util/appendinfo.c create mode 100644 src/include/optimizer/append.h create mode 100644 src/include/optimizer/appendinfo.h diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 3eae8316d5..12bb61f8cb 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -30,6 +30,8 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "optimizer/append.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/geqo.h" diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 029665b974..f52adc72d0 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -22,6 +22,7 @@ #include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index da0831de4e..6e321ec9e7 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -15,6 +15,7 @@ #include "postgres.h" #include "miscadmin.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 1ee7d9585b..62ad5eb5c2 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -39,6 +39,7 @@ #include "nodes/print.h" #endif #include "nodes/relation.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 96430a4cc7..70f37c593f 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -12,11 +12,6 @@ * case, but most of the heavy lifting for that is done elsewhere, * notably in prepjointree.c and allpaths.c. * - * There is also some code here to support planning of queries that use - * inheritance (SELECT FROM foo*). Inheritance trees are converted into - * append relations, and thenceforth share code with the UNION ALL case. - * - * * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -39,32 +34,21 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" -#include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" -#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" -#include "optimizer/var.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/selfuncs.h" #include "utils/syscache.h" -typedef struct -{ - PlannerInfo *root; - int nappinfos; - AppendRelInfo **appinfos; -} adjust_appendrel_attrs_context; - static RelOptInfo *recurse_set_operations(Node *setOp, PlannerInfo *root, List *colTypes, List *colCollations, bool junkOK, @@ -103,34 +87,6 @@ static List *generate_append_tlist(List *colTypes, List *colCollations, List *input_tlists, List *refnames_tlist); static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist); -static void expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, - RangeTblEntry *rte, Index rti); -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti, RelOptInfo *rel); -static void expand_partitioned_rtentry(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel); -static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel, - PlanRowMark *top_parentrc, Relation childrel, - RangeTblEntry **childrte_p, Index *childRTindex_p); -static void make_inh_translation_list(TupleDesc old_tupdesc, - TupleDesc new_tupdesc, - Oid from_rel, Oid to_rel, - Index newvarno, List **translated_vars); -static RelOptInfo *build_append_child_rel(PlannerInfo *root, - RelOptInfo *parent, - Index childRTindex); -static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); -static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars); -static Node *adjust_appendrel_attrs_mutator(Node *node, - adjust_appendrel_attrs_context *context); -static Relids adjust_child_relids(Relids relids, int nappinfos, - AppendRelInfo **appinfos); -static List *adjust_inherited_tlist(List *tlist, - AppendRelInfo *context); /* @@ -1467,1532 +1423,3 @@ generate_setop_grouplist(SetOperationStmt *op, List *targetlist) Assert(lg == NULL); return grouplist; } - - -/* - * expand_inherited_tables - * Expand each rangetable entry that represents an inheritance set - * into an "append relation". At the conclusion of this process, - * the "inh" flag is set in all and only those RTEs that are append - * relation parents. - */ -void -expand_inherited_tables(PlannerInfo *root) -{ - int orig_rtable_size; - Index rti; - - Assert(root->simple_rel_array_size > 0); - orig_rtable_size = root->simple_rel_array_size; - - /* - * expand_append_rtentry may add RTEs to parse->rtable. The function is - * expected to recursively handle any RTEs that it creates with inh=true. - * So just scan as far as the original end of the rtable list. - */ - for (rti = 1; rti < orig_rtable_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - RangeTblEntry *rte = root->simple_rte_array[rti]; - - /* there may be empty slots corresponding to non-baserel RTEs */ - if (brel == NULL) - continue; - - if (rte->inh) - expand_append_rtentry(root, brel, rte, rti); - } -} - -/* - * expand_append_rtentry - * This initializes RelOptInfos for an appendrel's child relations, if - * any - * - * 'rel' is the appendrel parent, whose range table entry ('rte') has been - * marked to require adding children. An appendrel parent could either - * be a subquery (if we flattened UNION ALL query) or a table that's known - * to have inheritance children. The latter consists of both regular - * inheritance parents and partitioned tables. - * - * For a subquery parent, there is not much to be done here because the - * children's RTEs are already present in the query, so we just initialize - * RelOptInfos for them. Also, the AppendRelInfos for child subqueries - * have already been added. - * - * For tables, we need to add the children to the range table and initialize - * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For - * a partitioned parent, we only add the children remaining after pruning. - * For regular inheritance parents, we find the children using - * find_all_inheritors and add all of them. - * - * If it turns out that there are no children, then we set rte->inh to false - * to let the caller know that only the parent table needs to be scanned. The - * caller can accordingly switch to a non-Append path. For a partitioned - * parent, that means an empty relation because parents themselves contain no - * data. - * - * For the regular inheritance case, the parent also gets another RTE with - * inh = false to represent it as an appendrel child. The original RTE is - * considered to represent the whole inheritance set. - */ -static void -expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, - Index rti) -{ - Assert(rte->inh); - /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ - Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); - - /* - * UNION ALL children already got RTEs and AppendRelInfos, so just build - * RelOptInfos and return. - * - * It might be a bit odd that this code is in this, because there is - * nothing to expand really. - */ - if (rte->rtekind == RTE_SUBQUERY) - { - ListCell *l; - - /* - * We don't need to use expand_planner_arrays in this case, because - * no new child RTEs are created. setup_simple_rel_arrays() and - * setup_append_rel_array would've considered these child RTEs when - * allocating space for various arrays. - */ - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst(l); - Index childRTindex = appinfo->child_relid; - - if (appinfo->parent_relid != rti) - continue; - - Assert(childRTindex < root->simple_rel_array_size); - Assert(root->simple_rte_array[childRTindex] != NULL); - - /* - * We set the correct value of baserestricinfo and - * baserestrict_min_security below. - */ - root->simple_rel_array[childRTindex] = - build_append_child_rel(root, rel, appinfo->child_relid); - } - } - else - { - Assert(rte->rtekind == RTE_RELATION); - Assert(has_subclass(rte->relid)); - - /* - * The rewriter should already have obtained an appropriate lock on - * each relation named in the query. However, for each child relation - * we add to the query, we must obtain an appropriate lock, because - * this will be the first use of those relations in the - * parse/rewrite/plan pipeline. Child rels should use the same - * lockmode as their parent. - */ - Assert(rte->rellockmode != NoLock); - - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - expand_partitioned_rtentry(root, rte, rti, rel); - else - expand_inherited_rtentry(root, rte, rti, rel); - } -} - -/* - * expand_inherited_rtentry - * Add entries for all the child tables to the query's rangetable, and - * build AppendRelInfo nodes for all the child tables and add them to - * root->append_rel_list. - * - * Note that the original RTE is considered to represent the whole - * inheritance set. The first of the generated RTEs is an RTE for the same - * table, but with inh = false, to represent the parent table in its role - * as a simple member of the inheritance set. - * - * A childless table is never considered to be an inheritance set. For - * regular inheritance, a parent RTE must always have at least two associated - * AppendRelInfos: one corresponding to the parent table as a simple member of - * inheritance set and one or more corresponding to the actual children. - */ -static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, - RelOptInfo *rel) -{ - Oid parentOID; - PlanRowMark *oldrc; - LOCKMODE lockmode = rte->rellockmode; - List *inhOIDs; - ListCell *l; - int num_children; - int num_children_added = 0; - - Assert(rte->rtekind == RTE_RELATION); - Assert(lockmode != NoLock); - parentOID = rte->relid; - - /* Scan for all members of inheritance set, acquire needed locks */ - inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); - - /* - * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite has_subclass() check performed by - * subquery_planner, if table once had a child but no longer does. - */ - num_children = list_length(inhOIDs); - if (num_children < 2) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks - * should've set isParent = true. We'll generate a new PlanRowMark for - * each child. - */ - oldrc = get_plan_rowmark(root->rowMarks, rti); - Assert(oldrc == NULL || oldrc->isParent); - - /* - * Must expand PlannerInfo arrays by num_children before we can add - * children. - */ - expand_planner_arrays(root, num_children); - - foreach(l, inhOIDs) - { - Oid childOID = lfirst_oid(l); - Relation newrelation; - RangeTblEntry *childrte; - Index childRTindex; - - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (RELATION_IS_OTHER_TEMP(newrelation)) - { - heap_close(newrelation, lockmode); - continue; - } - - (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, - newrelation, &childrte, - &childRTindex); - Assert(childrte != NULL); - /* All regular inheritance children are leaf children. */ - Assert(!childrte->inh); - Assert(childRTindex > 0); - - /* Close child relations, but keep locks */ - heap_close(newrelation, NoLock); - num_children_added++; - } - - /* - * If all children, including the parent (as child rel), were - * excluded, mark the parent rel as empty. If all the children were temp - * tables, pretend it's a non-inheritance situation; we don't need Append - * node in that case. The duplicate RTE we added for the parent table is - * harmless, so we don't bother to get rid of it; ditto for the useless - * PlanRowMark node. - */ - if (num_children_added == 0) - mark_dummy_rel(rel); - else if (num_children_added == 1) - rte->inh = false; - - /* - * Add junk columns needed by the row mark if any and also add the - * relevant expressions to the root parent's reltarget. - */ - if (oldrc) - { - List *tlist = add_rowmark_junk_columns(root, oldrc); - - build_base_rel_tlists(root, tlist); - } -} - -/* - * expand_partitioned_rtentry - * Prunes unnecessary partitions of a partitioned table and adds - * remaining ones to the Query and the PlannerInfo - * - * Partitions are added to the query in order in which they are found in - * the parent's PartitionDesc. - * - * Note: even though only the unpruned partitions will be added to the - * resulting plan, this still locks *all* partitions via find_all_inheritors - * when this function is called for the root partitioned table. - */ -static void -expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel) -{ - LOCKMODE lockmode = parentrte->rellockmode; - PlanRowMark *rootrc = NULL; - int i; - Bitmapset *partindexes; - Index rootParentRTindex = parentrel->inh_root_parent > 0 ? - parentrel->inh_root_parent : - parentRTindex; - - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - - /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, values of the - * indexes of partitioned relations that appear down in the tree will be - * bubbled up into root parent's list so that when we've created Paths for - * all the children, the root table's list will contain all such indexes. - */ - parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - - /* Perform pruning. */ - partindexes = prune_append_rel_partitions(parentrel); - - /* Must expand PlannerInfo arrays before we can add children. */ - expand_planner_arrays(root, bms_num_members(partindexes)); - - /* - * For partitioned tables, we also store the partition RelOptInfo - * pointers in the parent's RelOptInfo. - */ - parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * - parentrel->nparts); - - rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); - Assert(rootrc == NULL || rootrc->isParent); - i = -1; - while ((i = bms_next_member(partindexes, i)) >= 0) - { - Oid childOID = parentrel->part_oids[i]; - Relation newrelation; - RelOptInfo *childrel; - RangeTblEntry *childrte; - Index childRTindex; - - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); - Assert(!RELATION_IS_OTHER_TEMP(newrelation)); - - /* - * A partitioned child table with 0 children is a dummy rel, so don't - * bother creating planner objects for it. - */ - if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && - RelationGetPartitionDesc(newrelation)->nparts == 0) - { - heap_close(newrelation, NoLock); - continue; - } - - childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, - parentrel, rootrc, newrelation, - &childrte, &childRTindex); - Assert(childrel != NULL); - parentrel->part_rels[i] = childrel; - - /* Close child relations, but keep locks */ - heap_close(newrelation, NoLock); - - /* If the child is partitioned itself, expand it too. */ - if (childrel->part_scheme) - { - Assert(childrte->inh); - expand_partitioned_rtentry(root, childrte, childRTindex, - childrel); - } - } - - /* - * Add junk columns needed by the row mark if any and also add the - * relevant expressions to the root parent's reltarget. - */ - if (rootrc) - { - List *tlist = add_rowmark_junk_columns(root, rootrc); - - build_base_rel_tlists(root, tlist); - } -} - -/* - * add_inheritance_child_rel - * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally - * a RelOptInfo for an inheritance child relation. - * - * The return value is the RelOptInfo that's added. - * - * PlanRowMarks still carry the top-parent's RTI, and the top-parent's - * allMarkTypes field still accumulates values from all descendents. - * - * "parentrte" and "parentRTindex" are immediate parent's RTE and - * RTI. "top_parentrc" is top parent's PlanRowMark. - */ -static RelOptInfo * -add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel, - PlanRowMark *top_parentrc, Relation childrel, - RangeTblEntry **childrte_p, Index *childRTindex_p) -{ - Query *parse = root->parse; - Oid childOID = RelationGetRelid(childrel); - RangeTblEntry *childrte; - Index childRTindex; - AppendRelInfo *appinfo; - RelOptInfo *childrelopt; - - /* - * Build an RTE for the child, and attach to query's rangetable list. We - * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh appropriately. Also, set requiredPerms to zero - * since all required permissions checks are done on the original RTE. - * Likewise, set the child's securityQuals to empty, because we only want - * to apply the parent's RLS conditions regardless of what RLS properties - * individual children may have. (This is an intentional choice to make - * inherited RLS work like regular permissions checks.) The parent - * securityQuals will be propagated to children along with other base - * restriction clauses, so we don't need to do it here. - */ - childrte = copyObject(parentrte); - *childrte_p = childrte; - childrte->relid = childOID; - childrte->relkind = childrel->rd_rel->relkind; - /* - * A partitioned child will need to be expanded as an append parent - * itself, so set its inh to true. - */ - childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); - childrte->requiredPerms = 0; - childrte->securityQuals = NIL; - parse->rtable = lappend(parse->rtable, childrte); - childRTindex = list_length(parse->rtable); - *childRTindex_p = childRTindex; - - /* Create an AppendRelInfo and add it to planner's global list. */ - appinfo = make_append_rel_info(parentrel, parentrte, - childrel->rd_att, - childOID, - childrel->rd_rel->reltype, - childRTindex); - root->append_rel_list = lappend(root->append_rel_list, appinfo); - - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childrte->relid != parentrte->relid) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } - - /* - * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE. - */ - if (top_parentrc) - { - PlanRowMark *childrc = makeNode(PlanRowMark); - - childrc->rti = childRTindex; - childrc->prti = top_parentrc->rti; - childrc->rowmarkId = top_parentrc->rowmarkId; - /* Reselect rowmark type, because relkind might not match parent */ - childrc->markType = select_rowmark_type(childrte, - top_parentrc->strength); - childrc->allMarkTypes = (1 << childrc->markType); - childrc->strength = top_parentrc->strength; - childrc->waitPolicy = top_parentrc->waitPolicy; - - /* - * We mark RowMarks for partitioned child tables as parent RowMarks so - * that the executor ignores them (except their existence means that - * the child tables be locked using appropriate mode). - */ - childrc->isParent = (childrte->relkind == RELKIND_PARTITIONED_TABLE); - - /* Include child's rowmark type in top parent's allMarkTypes */ - top_parentrc->allMarkTypes |= childrc->allMarkTypes; - - root->rowMarks = lappend(root->rowMarks, childrc); - } - - /* - * Add the RelOptInfo. Even though we may not really scan this relation - * for reasons such as contradictory quals, we still need to create one, - * because for every RTE in the query's range table, there must be an - * accompanying RelOptInfo. - */ - - /* First, store the RTE and appinfos into planner arrays. */ - Assert(root->simple_rte_array[childRTindex] == NULL); - root->simple_rte_array[childRTindex] = childrte; - Assert(root->append_rel_array[childRTindex] == NULL); - root->append_rel_array[childRTindex] = appinfo; - - childrelopt = build_append_child_rel(root, parentrel, childRTindex); - Assert(childrelopt != NULL); - - return childrelopt; -} - -/* - * make_inh_translation_list - * Build the list of translations from parent Vars to child Vars for - * an inheritance child. - * - * For paranoia's sake, we match type/collation as well as attribute name. - */ -static void -make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, - Oid from_rel, Oid to_rel, - Index newvarno, List **translated_vars) -{ - List *vars = NIL; - int oldnatts = old_tupdesc->natts; - int newnatts = new_tupdesc->natts; - int old_attno; - int new_attno = 0; - - for (old_attno = 0; old_attno < oldnatts; old_attno++) - { - Form_pg_attribute att; - char *attname; - Oid atttypid; - int32 atttypmod; - Oid attcollation; - - att = TupleDescAttr(old_tupdesc, old_attno); - if (att->attisdropped) - { - /* Just put NULL into this list entry */ - vars = lappend(vars, NULL); - continue; - } - attname = NameStr(att->attname); - atttypid = att->atttypid; - atttypmod = att->atttypmod; - attcollation = att->attcollation; - - /* - * When we are generating the "translation list" for the parent table - * of an inheritance set, no need to search for matches. - */ - if (from_rel == to_rel) - { - vars = lappend(vars, makeVar(newvarno, - (AttrNumber) (old_attno + 1), - atttypid, - atttypmod, - attcollation, - 0)); - continue; - } - - /* - * Otherwise we have to search for the matching column by name. - * There's no guarantee it'll have the same column position, because - * of cases like ALTER TABLE ADD COLUMN and multiple inheritance. - * However, in simple cases, the relative order of columns is mostly - * the same in both relations, so try the column of newrelation that - * follows immediately after the one that we just found, and if that - * fails, let syscache handle it. - */ - if (new_attno >= newnatts || - (att = TupleDescAttr(new_tupdesc, new_attno))->attisdropped || - strcmp(attname, NameStr(att->attname)) != 0) - { - HeapTuple newtup; - - newtup = SearchSysCacheAttName(to_rel, attname); - if (!newtup) - elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, get_rel_name(to_rel)); - new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; - ReleaseSysCache(newtup); - - att = TupleDescAttr(new_tupdesc, new_attno); - } - - /* Found it, check type and collation match */ - if (atttypid != att->atttypid || atttypmod != att->atttypmod) - elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, get_rel_name(to_rel)); - if (attcollation != att->attcollation) - elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, get_rel_name(to_rel)); - - vars = lappend(vars, makeVar(newvarno, - (AttrNumber) (new_attno + 1), - atttypid, - atttypmod, - attcollation, - 0)); - new_attno++; - } - - *translated_vars = vars; -} - -/* - * translate_col_privs - * Translate a bitmapset representing per-column privileges from the - * parent rel's attribute numbering to the child's. - * - * The only surprise here is that we don't translate a parent whole-row - * reference into a child whole-row reference. That would mean requiring - * permissions on all child columns, which is overly strict, since the - * query is really only going to reference the inherited columns. Instead - * we set the per-column bits for all inherited columns. - */ -static Bitmapset * -translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars) -{ - Bitmapset *child_privs = NULL; - bool whole_row; - int attno; - ListCell *lc; - - /* System attributes have the same numbers in all tables */ - for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) - { - if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - attno - FirstLowInvalidHeapAttributeNumber); - } - - /* Check if parent has whole-row reference */ - whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, - parent_privs); - - /* And now translate the regular user attributes, using the vars list */ - attno = InvalidAttrNumber; - foreach(lc, translated_vars) - { - Var *var = lfirst_node(Var, lc); - - attno++; - if (var == NULL) /* ignore dropped columns */ - continue; - if (whole_row || - bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - var->varattno - FirstLowInvalidHeapAttributeNumber); - } - - return child_privs; -} - -/* - * build_append_child_rel - * Build a RelOptInfo for child relation of an append rel - * - * After creating the RelOptInfo for the given child RT index, it goes on to - * initialize some of its fields based on the parent RelOptInfo. - * - * If the quals in baserestrictinfo turn out to be self-contradictory, - * RelOptInfo is marked dummy before returning. - */ -static RelOptInfo * -build_append_child_rel(PlannerInfo *root, - RelOptInfo *parent, - Index childRTindex) -{ - RelOptInfo *childrel; - RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; - AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; - List *childquals; - ListCell *lc; - bool have_const_false_cq; - Index cq_min_security; - - /* Build the RelOptInfo. */ - childrel = build_simple_rel(root, childRTindex, parent); - - /* - * Propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - */ - childrel->direct_lateral_relids = parent->direct_lateral_relids; - childrel->lateral_relids = parent->lateral_relids; - childrel->lateral_referencers = parent->lateral_referencers; - - /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) - */ - childquals = false; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, parent->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual = (Node *) rinfo->clause; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, childqual, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might - * have securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals.) Pull any such securityQuals up into the - * baserestrictinfo for the child. This is similar to - * process_security_barrier_quals() for the parent rel, except - * that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* - * not likely that we'd see constants here, so no - * check - */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, - false, - false, - security_level, - NULL, NULL, - NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* Set child's version of baserestrictinfo. */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - } - - return childrel; -} - -/* - * add_rowmark_junk_columns - * Add necessary junk columns for rowmarked inheritance parent rel. - * - * These values are needed for locking of rels selected FOR UPDATE/SHARE, and - * to do EvalPlanQual rechecking. See comments for PlanRowMark in - * plannodes.h. - */ -static List * -add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) -{ - List *tlist = root->processed_tlist; - Var *var; - char resname[32]; - TargetEntry *tle; - - if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) - { - /* Need to fetch TID */ - var = makeVar(rc->rti, - SelfItemPointerAttributeNumber, - TIDOID, - -1, - InvalidOid, - 0); - snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - } - if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) - { - /* Need the whole row as a junk var */ - var = makeWholeRowVar(root->simple_rte_array[rc->rti], - rc->rti, - 0, - false); - snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - } - - /* For inheritance cases, always fetch the tableoid too. */ - var = makeVar(rc->rti, - TableOidAttributeNumber, - OIDOID, - -1, - InvalidOid, - 0); - snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - - return tlist; -} - -AppendRelInfo * -make_append_rel_info(RelOptInfo *parentrel, RangeTblEntry *parentrte, - TupleDesc childdesc, Oid childoid, Oid childreltype, - Index childRTindex) -{ - AppendRelInfo *appinfo = makeNode(AppendRelInfo); - - appinfo->parent_relid = parentrel->relid; - appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->reltype; - appinfo->child_reltype = childreltype; - make_inh_translation_list(parentrel->tupdesc, childdesc, - parentrte->relid, childoid, - childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = parentrte->relid; - - return appinfo; -} - -/* - * adjust_appendrel_attrs - * Copy the specified query or expression and translate Vars referring to a - * parent rel to refer to the corresponding child rel instead. We also - * update rtindexes appearing outside Vars, such as resultRelation and - * jointree relids. - * - * Note: this is only applied after conversion of sublinks to subplans, - * so we don't need to cope with recursion into sub-queries. - * - * Note: this is not hugely different from what pullup_replace_vars() does; - * maybe we should try to fold the two routines together. - */ -Node * -adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, - AppendRelInfo **appinfos) -{ - Node *result; - adjust_appendrel_attrs_context context; - - context.root = root; - context.nappinfos = nappinfos; - context.appinfos = appinfos; - - /* If there's nothing to adjust, don't call this function. */ - Assert(nappinfos >= 1 && appinfos != NULL); - - /* - * Must be prepared to start with a Query or a bare expression tree. - */ - if (node && IsA(node, Query)) - { - Query *newnode; - int cnt; - - newnode = query_tree_mutator((Query *) node, - adjust_appendrel_attrs_mutator, - (void *) &context, - QTW_IGNORE_RC_SUBQUERIES); - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (newnode->resultRelation == appinfo->parent_relid) - { - newnode->resultRelation = appinfo->child_relid; - /* Fix tlist resnos too, if it's inherited UPDATE */ - if (newnode->commandType == CMD_UPDATE) - newnode->targetList = - adjust_inherited_tlist(newnode->targetList, - appinfo); - break; - } - } - - result = (Node *) newnode; - } - else - result = adjust_appendrel_attrs_mutator(node, &context); - - return result; -} - -static Node * -adjust_appendrel_attrs_mutator(Node *node, - adjust_appendrel_attrs_context *context) -{ - AppendRelInfo **appinfos = context->appinfos; - int nappinfos = context->nappinfos; - int cnt; - - if (node == NULL) - return NULL; - if (IsA(node, Var)) - { - Var *var = (Var *) copyObject(node); - AppendRelInfo *appinfo = NULL; - - for (cnt = 0; cnt < nappinfos; cnt++) - { - if (var->varno == appinfos[cnt]->parent_relid) - { - appinfo = appinfos[cnt]; - break; - } - } - - if (var->varlevelsup == 0 && appinfo) - { - var->varno = appinfo->child_relid; - var->varnoold = appinfo->child_relid; - if (var->varattno > 0) - { - Node *newnode; - - if (var->varattno > list_length(appinfo->translated_vars)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - var->varattno, get_rel_name(appinfo->parent_reloid)); - newnode = copyObject(list_nth(appinfo->translated_vars, - var->varattno - 1)); - if (newnode == NULL) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - var->varattno, get_rel_name(appinfo->parent_reloid)); - return newnode; - } - else if (var->varattno == 0) - { - /* - * Whole-row Var: if we are dealing with named rowtypes, we - * can use a whole-row Var for the child table plus a coercion - * step to convert the tuple layout to the parent's rowtype. - * Otherwise we have to generate a RowExpr. - */ - if (OidIsValid(appinfo->child_reltype)) - { - Assert(var->vartype == appinfo->parent_reltype); - if (appinfo->parent_reltype != appinfo->child_reltype) - { - ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr); - - r->arg = (Expr *) var; - r->resulttype = appinfo->parent_reltype; - r->convertformat = COERCE_IMPLICIT_CAST; - r->location = -1; - /* Make sure the Var node has the right type ID, too */ - var->vartype = appinfo->child_reltype; - return (Node *) r; - } - } - else - { - /* - * Build a RowExpr containing the translated variables. - * - * In practice var->vartype will always be RECORDOID here, - * so we need to come up with some suitable column names. - * We use the parent RTE's column names. - * - * Note: we can't get here for inheritance cases, so there - * is no need to worry that translated_vars might contain - * some dummy NULLs. - */ - RowExpr *rowexpr; - List *fields; - RangeTblEntry *rte; - - rte = rt_fetch(appinfo->parent_relid, - context->root->parse->rtable); - fields = copyObject(appinfo->translated_vars); - rowexpr = makeNode(RowExpr); - rowexpr->args = fields; - rowexpr->row_typeid = var->vartype; - rowexpr->row_format = COERCE_IMPLICIT_CAST; - rowexpr->colnames = copyObject(rte->eref->colnames); - rowexpr->location = -1; - - return (Node *) rowexpr; - } - } - /* system attributes don't need any other translation */ - } - return (Node *) var; - } - if (IsA(node, CurrentOfExpr)) - { - CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node); - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (cexpr->cvarno == appinfo->parent_relid) - { - cexpr->cvarno = appinfo->child_relid; - break; - } - } - return (Node *) cexpr; - } - if (IsA(node, RangeTblRef)) - { - RangeTblRef *rtr = (RangeTblRef *) copyObject(node); - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (rtr->rtindex == appinfo->parent_relid) - { - rtr->rtindex = appinfo->child_relid; - break; - } - } - return (Node *) rtr; - } - if (IsA(node, JoinExpr)) - { - /* Copy the JoinExpr node with correct mutation of subnodes */ - JoinExpr *j; - AppendRelInfo *appinfo; - - j = (JoinExpr *) expression_tree_mutator(node, - adjust_appendrel_attrs_mutator, - (void *) context); - /* now fix JoinExpr's rtindex (probably never happens) */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - appinfo = appinfos[cnt]; - - if (j->rtindex == appinfo->parent_relid) - { - j->rtindex = appinfo->child_relid; - break; - } - } - return (Node *) j; - } - if (IsA(node, PlaceHolderVar)) - { - /* Copy the PlaceHolderVar node with correct mutation of subnodes */ - PlaceHolderVar *phv; - - phv = (PlaceHolderVar *) expression_tree_mutator(node, - adjust_appendrel_attrs_mutator, - (void *) context); - /* now fix PlaceHolderVar's relid sets */ - if (phv->phlevelsup == 0) - phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos, - context->appinfos); - return (Node *) phv; - } - - /* - * This is needed, because inheritance_make_rel_from_joinlist needs to - * translate root->join_info_list executing make_rel_from_joinlist for a - * given child. - */ - if (IsA(node, SpecialJoinInfo)) - { - SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; - SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); - - memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); - newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, - context->nappinfos, - context->appinfos); - newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, - context->nappinfos, - context->appinfos); - newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, - context->nappinfos, - context->appinfos); - newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, - context->nappinfos, - context->appinfos); - newinfo->semi_rhs_exprs = - (List *) expression_tree_mutator((Node *) - oldinfo->semi_rhs_exprs, - adjust_appendrel_attrs_mutator, - (void *) context); - return (Node *) newinfo; - } - - /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, AppendRelInfo)); - Assert(!IsA(node, PlaceHolderInfo)); - Assert(!IsA(node, MinMaxAggInfo)); - - /* - * We have to process RestrictInfo nodes specially. (Note: although - * set_append_rel_pathlist will hide RestrictInfos in the parent's - * baserestrictinfo list from us, it doesn't hide those in joininfo.) - */ - if (IsA(node, RestrictInfo)) - { - RestrictInfo *oldinfo = (RestrictInfo *) node; - RestrictInfo *newinfo = makeNode(RestrictInfo); - - /* Copy all flat-copiable fields */ - memcpy(newinfo, oldinfo, sizeof(RestrictInfo)); - - /* Recursively fix the clause itself */ - newinfo->clause = (Expr *) - adjust_appendrel_attrs_mutator((Node *) oldinfo->clause, context); - - /* and the modified version, if an OR clause */ - newinfo->orclause = (Expr *) - adjust_appendrel_attrs_mutator((Node *) oldinfo->orclause, context); - - /* adjust relid sets too */ - newinfo->clause_relids = adjust_child_relids(oldinfo->clause_relids, - context->nappinfos, - context->appinfos); - newinfo->required_relids = adjust_child_relids(oldinfo->required_relids, - context->nappinfos, - context->appinfos); - newinfo->outer_relids = adjust_child_relids(oldinfo->outer_relids, - context->nappinfos, - context->appinfos); - newinfo->nullable_relids = adjust_child_relids(oldinfo->nullable_relids, - context->nappinfos, - context->appinfos); - newinfo->left_relids = adjust_child_relids(oldinfo->left_relids, - context->nappinfos, - context->appinfos); - newinfo->right_relids = adjust_child_relids(oldinfo->right_relids, - context->nappinfos, - context->appinfos); - - /* - * Reset cached derivative fields, since these might need to have - * different values when considering the child relation. Note we - * don't reset left_ec/right_ec: each child variable is implicitly - * equivalent to its parent, so still a member of the same EC if any. - */ - newinfo->eval_cost.startup = -1; - newinfo->norm_selec = -1; - newinfo->outer_selec = -1; - newinfo->left_em = NULL; - newinfo->right_em = NULL; - newinfo->scansel_cache = NIL; - newinfo->left_bucketsize = -1; - newinfo->right_bucketsize = -1; - newinfo->left_mcvfreq = -1; - newinfo->right_mcvfreq = -1; - - return (Node *) newinfo; - } - - /* - * NOTE: we do not need to recurse into sublinks, because they should - * already have been converted to subplans before we see them. - */ - Assert(!IsA(node, SubLink)); - Assert(!IsA(node, Query)); - - return expression_tree_mutator(node, adjust_appendrel_attrs_mutator, - (void *) context); -} - -/* - * Substitute child relids for parent relids in a Relid set. The array of - * appinfos specifies the substitutions to be performed. - */ -static Relids -adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos) -{ - Bitmapset *result = NULL; - int cnt; - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - /* Remove parent, add child */ - if (bms_is_member(appinfo->parent_relid, relids)) - { - /* Make a copy if we are changing the set. */ - if (!result) - result = bms_copy(relids); - - result = bms_del_member(result, appinfo->parent_relid); - result = bms_add_member(result, appinfo->child_relid); - } - } - - /* If we made any changes, return the modified copy. */ - if (result) - return result; - - /* Otherwise, return the original set without modification. */ - return relids; -} - -/* - * Replace any relid present in top_parent_relids with its child in - * child_relids. Members of child_relids can be multiple levels below top - * parent in the partition hierarchy. - */ -Relids -adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, - Relids child_relids, Relids top_parent_relids) -{ - AppendRelInfo **appinfos; - int nappinfos; - Relids parent_relids = NULL; - Relids result; - Relids tmp_result = NULL; - int cnt; - - /* - * If the given relids set doesn't contain any of the top parent relids, - * it will remain unchanged. - */ - if (!bms_overlap(relids, top_parent_relids)) - return relids; - - appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); - - /* Construct relids set for the immediate parent of the given child. */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); - } - - /* Recurse if immediate parent is not the top parent. */ - if (!bms_equal(parent_relids, top_parent_relids)) - { - tmp_result = adjust_child_relids_multilevel(root, relids, - parent_relids, - top_parent_relids); - relids = tmp_result; - } - - result = adjust_child_relids(relids, nappinfos, appinfos); - - /* Free memory consumed by any intermediate result. */ - if (tmp_result) - bms_free(tmp_result); - bms_free(parent_relids); - pfree(appinfos); - - return result; -} - -/* - * Adjust the targetlist entries of an inherited UPDATE operation - * - * The expressions have already been fixed, but we have to make sure that - * the target resnos match the child table (they may not, in the case of - * a column that was added after-the-fact by ALTER TABLE). In some cases - * this can force us to re-order the tlist to preserve resno ordering. - * (We do all this work in special cases so that preptlist.c is fast for - * the typical case.) - * - * The given tlist has already been through expression_tree_mutator; - * therefore the TargetEntry nodes are fresh copies that it's okay to - * scribble on. - * - * Note that this is not needed for INSERT because INSERT isn't inheritable. - */ -static List * -adjust_inherited_tlist(List *tlist, AppendRelInfo *context) -{ - bool changed_it = false; - ListCell *tl; - List *new_tlist; - bool more; - int attrno; - - /* This should only happen for an inheritance case, not UNION ALL */ - Assert(OidIsValid(context->parent_reloid)); - - /* Scan tlist and update resnos to match attnums of child rel */ - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - Var *childvar; - - if (tle->resjunk) - continue; /* ignore junk items */ - - /* Look up the translation of this column: it must be a Var */ - if (tle->resno <= 0 || - tle->resno > list_length(context->translated_vars)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - tle->resno, get_rel_name(context->parent_reloid)); - childvar = (Var *) list_nth(context->translated_vars, tle->resno - 1); - if (childvar == NULL || !IsA(childvar, Var)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - tle->resno, get_rel_name(context->parent_reloid)); - - if (tle->resno != childvar->varattno) - { - tle->resno = childvar->varattno; - changed_it = true; - } - } - - /* - * If we changed anything, re-sort the tlist by resno, and make sure - * resjunk entries have resnos above the last real resno. The sort - * algorithm is a bit stupid, but for such a seldom-taken path, small is - * probably better than fast. - */ - if (!changed_it) - return tlist; - - new_tlist = NIL; - more = true; - for (attrno = 1; more; attrno++) - { - more = false; - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - - if (tle->resjunk) - continue; /* ignore junk items */ - - if (tle->resno == attrno) - new_tlist = lappend(new_tlist, tle); - else if (tle->resno > attrno) - more = true; - } - } - - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - - if (!tle->resjunk) - continue; /* here, ignore non-junk items */ - - tle->resno = attrno; - new_tlist = lappend(new_tlist, tle); - attrno++; - } - - return new_tlist; -} - -/* - * adjust_appendrel_attrs_multilevel - * Apply Var translations from a toplevel appendrel parent down to a child. - * - * In some cases we need to translate expressions referencing a parent relation - * to reference an appendrel child that's multiple levels removed from it. - */ -Node * -adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, - Relids child_relids, - Relids top_parent_relids) -{ - AppendRelInfo **appinfos; - Bitmapset *parent_relids = NULL; - int nappinfos; - int cnt; - - Assert(bms_num_members(child_relids) == bms_num_members(top_parent_relids)); - - appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); - - /* Construct relids set for the immediate parent of given child. */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); - } - - /* Recurse if immediate parent is not the top parent. */ - if (!bms_equal(parent_relids, top_parent_relids)) - node = adjust_appendrel_attrs_multilevel(root, node, parent_relids, - top_parent_relids); - - /* Now translate for this child */ - node = adjust_appendrel_attrs(root, node, nappinfos, appinfos); - - pfree(appinfos); - - return node; -} - -/* - * Construct the SpecialJoinInfo for a child-join by translating - * SpecialJoinInfo for the join between parents. left_relids and right_relids - * are the relids of left and right side of the join respectively. - */ -SpecialJoinInfo * -build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, - Relids left_relids, Relids right_relids) -{ - SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo); - AppendRelInfo **left_appinfos; - int left_nappinfos; - AppendRelInfo **right_appinfos; - int right_nappinfos; - - memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo)); - left_appinfos = find_appinfos_by_relids(root, left_relids, - &left_nappinfos); - right_appinfos = find_appinfos_by_relids(root, right_relids, - &right_nappinfos); - - sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand, - left_nappinfos, left_appinfos); - sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand, - right_nappinfos, - right_appinfos); - sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand, - left_nappinfos, left_appinfos); - sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand, - right_nappinfos, - right_appinfos); - sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root, - (Node *) sjinfo->semi_rhs_exprs, - right_nappinfos, - right_appinfos); - - pfree(left_appinfos); - pfree(right_appinfos); - - return sjinfo; -} - -/* - * find_appinfos_by_relids - * Find AppendRelInfo structures for all relations specified by relids. - * - * The AppendRelInfos are returned in an array, which can be pfree'd by the - * caller. *nappinfos is set to the number of entries in the array. - */ -AppendRelInfo ** -find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos) -{ - AppendRelInfo **appinfos; - int cnt = 0; - int i; - - *nappinfos = bms_num_members(relids); - appinfos = (AppendRelInfo **) palloc(sizeof(AppendRelInfo *) * *nappinfos); - - i = -1; - while ((i = bms_next_member(relids, i)) >= 0) - { - AppendRelInfo *appinfo = root->append_rel_array[i]; - - if (!appinfo) - elog(ERROR, "child rel %d not found in append_rel_array", i); - - appinfos[cnt++] = appinfo; - } - return appinfos; -} diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile index da9ccf32b4..d166030a10 100644 --- a/src/backend/optimizer/util/Makefile +++ b/src/backend/optimizer/util/Makefile @@ -12,7 +12,8 @@ subdir = src/backend/optimizer/util top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = clauses.o joininfo.o orclauses.o pathnode.o placeholder.o \ - plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o +OBJS = append.o appendinfo.o clauses.o joininfo.o orclauses.o pathnode.o \ + placeholder.o plancat.o predtest.o relnode.o restrictinfo.o tlist.o \ + var.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/optimizer/util/append.c b/src/backend/optimizer/util/append.c new file mode 100644 index 0000000000..e78a61d613 --- /dev/null +++ b/src/backend/optimizer/util/append.c @@ -0,0 +1,776 @@ +/*------------------------------------------------------------------------- + * + * append.c + * Routines to process children of an appendrel parent + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/optimizer/path/append.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <limits.h> + +#include "access/sysattr.h" +#include "catalog/partition.h" +#include "catalog/pg_class.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_type.h" +#include "nodes/makefuncs.h" +#include "optimizer/append.h" +#include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" +#include "optimizer/plancat.h" +#include "optimizer/planmain.h" +#include "optimizer/planner.h" +#include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" +#include "optimizer/tlist.h" +#include "optimizer/var.h" +#include "partitioning/partprune.h" +#include "utils/rel.h" + +static void expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti); +static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, + Index rti, RelOptInfo *rel); +static void expand_partitioned_rtentry(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p); +static RelOptInfo *build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); + + +/* + * expand_inherited_tables + * Expand each rangetable entry that represents an inheritance set + * into an "append relation". At the conclusion of this process, + * the "inh" flag is set in all and only those RTEs that are append + * relation parents. + */ +void +expand_inherited_tables(PlannerInfo *root) +{ + int orig_rtable_size; + Index rti; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; + + /* + * expand_append_rtentry may add RTEs to parse->rtable. The function is + * expected to recursively handle any RTEs that it creates with inh=true. + * So just scan as far as the original end of the rtable list. + */ + for (rti = 1; rti < orig_rtable_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; + + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + if (rte->inh) + expand_append_rtentry(root, brel, rte, rti); + } +} + +/* + * expand_append_rtentry + * This initializes RelOptInfos for an appendrel's child relations, if + * any + * + * 'rel' is the appendrel parent, whose range table entry ('rte') has been + * marked to require adding children. An appendrel parent could either + * be a subquery (if we flattened UNION ALL query) or a table that's known + * to have inheritance children. The latter consists of both regular + * inheritance parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as an appendrel child. The original RTE is + * considered to represent the whole inheritance set. + */ +static void +expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, + Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_append_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_inherited_rtentry(root, rte, rti, rel); + } +} + +/* + * expand_inherited_rtentry + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. + * + * Note that the original RTE is considered to represent the whole + * inheritance set. The first of the generated RTEs is an RTE for the same + * table, but with inh = false, to represent the parent table in its role + * as a simple member of the inheritance set. + * + * A childless table is never considered to be an inheritance set. For + * regular inheritance, a parent RTE must always have at least two associated + * AppendRelInfos: one corresponding to the parent table as a simple member of + * inheritance set and one or more corresponding to the actual children. + */ +static void +expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, + RelOptInfo *rel) +{ + Oid parentOID; + PlanRowMark *oldrc; + LOCKMODE lockmode = rte->rellockmode; + List *inhOIDs; + ListCell *l; + int num_children; + int num_children_added = 0; + + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); + parentOID = rte->relid; + + /* Scan for all members of inheritance set, acquire needed locks */ + inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); + + /* + * Check that there's at least one descendant, else treat as no-child + * case. This could happen despite has_subclass() check performed by + * subquery_planner, if table once had a child but no longer does. + */ + num_children = list_length(inhOIDs); + if (num_children < 2) + { + /* Clear flag before returning */ + rte->inh = false; + return; + } + + /* + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. + */ + oldrc = get_plan_rowmark(root->rowMarks, rti); + Assert(oldrc == NULL || oldrc->isParent); + + /* + * Must expand PlannerInfo arrays by num_children before we can add + * children. + */ + expand_planner_arrays(root, num_children); + + foreach(l, inhOIDs) + { + Oid childOID = lfirst_oid(l); + Relation newrelation; + RangeTblEntry *childrte; + Index childRTindex; + + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + + /* + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. + */ + if (RELATION_IS_OTHER_TEMP(newrelation)) + { + heap_close(newrelation, lockmode); + continue; + } + + (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation, &childrte, + &childRTindex); + Assert(childrte != NULL); + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + Assert(childRTindex > 0); + + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; + } + + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } +} + +/* + * expand_partitioned_rtentry + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * when this function is called for the root partitioned table. + */ +static void +expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel) +{ + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; + int i; + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; + + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); + + /* + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. + */ + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); + + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + expand_planner_arrays(root, bms_num_members(partindexes)); + + /* + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. + */ + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); + + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) + { + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + RangeTblEntry *childrte; + Index childRTindex; + + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); + + /* + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. + */ + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } + + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation, + &childrte, &childRTindex); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; + + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + + /* If the child is partitioned itself, expand it too. */ + if (childrel->part_scheme) + { + Assert(childrte->inh); + expand_partitioned_rtentry(root, childrte, childRTindex, + childrel); + } + } + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); + } +} + +/* + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. + * + * The return value is the RelOptInfo that's added. + * + * PlanRowMarks still carry the top-parent's RTI, and the top-parent's + * allMarkTypes field still accumulates values from all descendents. + * + * "parentrte" and "parentRTindex" are immediate parent's RTE and + * RTI. "top_parentrc" is top parent's PlanRowMark. + */ +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p) +{ + Query *parse = root->parse; + Oid childOID = RelationGetRelid(childrel); + RangeTblEntry *childrte; + Index childRTindex; + AppendRelInfo *appinfo; + RelOptInfo *childrelopt; + + /* + * Build an RTE for the child, and attach to query's rangetable list. We + * copy most fields of the parent's RTE, but replace relation OID and + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties + * individual children may have. (This is an intentional choice to make + * inherited RLS work like regular permissions checks.) The parent + * securityQuals will be propagated to children along with other base + * restriction clauses, so we don't need to do it here. + */ + childrte = copyObject(parentrte); + *childrte_p = childrte; + childrte->relid = childOID; + childrte->relkind = childrel->rd_rel->relkind; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); + childrte->requiredPerms = 0; + childrte->securityQuals = NIL; + parse->rtable = lappend(parse->rtable, childrte); + childRTindex = list_length(parse->rtable); + *childRTindex_p = childRTindex; + + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + childrel->rd_att, + childOID, + childrel->rd_rel->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); + + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); + } + + /* + * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE. + */ + if (top_parentrc) + { + PlanRowMark *childrc = makeNode(PlanRowMark); + + childrc->rti = childRTindex; + childrc->prti = top_parentrc->rti; + childrc->rowmarkId = top_parentrc->rowmarkId; + /* Reselect rowmark type, because relkind might not match parent */ + childrc->markType = select_rowmark_type(childrte, + top_parentrc->strength); + childrc->allMarkTypes = (1 << childrc->markType); + childrc->strength = top_parentrc->strength; + childrc->waitPolicy = top_parentrc->waitPolicy; + + /* + * We mark RowMarks for partitioned child tables as parent RowMarks so + * that the executor ignores them (except their existence means that + * the child tables be locked using appropriate mode). + */ + childrc->isParent = (childrte->relkind == RELKIND_PARTITIONED_TABLE); + + /* Include child's rowmark type in top parent's allMarkTypes */ + top_parentrc->allMarkTypes |= childrc->allMarkTypes; + + root->rowMarks = lappend(root->rowMarks, childrc); + } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need to create one, + * because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_append_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; +} + +/* + * build_append_child_rel + * Build a RelOptInfo for child relation of an append rel + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields based on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turn out to be self-contradictory, the + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + List *childquals; + ListCell *lc; + bool have_const_false_cq; + Index cq_min_security; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = false; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, parent->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual = (Node *) rinfo->clause; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, childqual, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might + * have securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals.) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except + * that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* + * not likely that we'd see constants here, so no + * check + */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, + false, + false, + security_level, + NULL, NULL, + NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* Set child's version of baserestrictinfo. */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; +} diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c new file mode 100644 index 0000000000..9502535e53 --- /dev/null +++ b/src/backend/optimizer/util/appendinfo.c @@ -0,0 +1,851 @@ +/*------------------------------------------------------------------------- + * + * appendinfo.c + * Routines for mapping between append parent(s) and children + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/optimizer/path/appendinfo.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" +#include "parser/parsetree.h" +#include "utils/rel.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" + +typedef struct +{ + PlannerInfo *root; + int nappinfos; + AppendRelInfo **appinfos; +} adjust_appendrel_attrs_context; + +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); +static Node *adjust_appendrel_attrs_mutator(Node *node, + adjust_appendrel_attrs_context *context); +static Relids adjust_child_relids(Relids relids, int nappinfos, + AppendRelInfo **appinfos); +static List *adjust_inherited_tlist(List *tlist, + AppendRelInfo *context); + +AppendRelInfo * +make_append_rel_info(RelOptInfo *parentrel, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex) +{ + AppendRelInfo *appinfo = makeNode(AppendRelInfo); + + appinfo->parent_relid = parentrel->relid; + appinfo->child_relid = childRTindex; + appinfo->parent_reltype = parentrel->reltype; + appinfo->child_reltype = childreltype; + make_inh_translation_list(parentrel->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, + &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; + + return appinfo; +} + +/* + * adjust_appendrel_attrs + * Copy the specified query or expression and translate Vars referring to a + * parent rel to refer to the corresponding child rel instead. We also + * update rtindexes appearing outside Vars, such as resultRelation and + * jointree relids. + * + * Note: this is only applied after conversion of sublinks to subplans, + * so we don't need to cope with recursion into sub-queries. + * + * Note: this is not hugely different from what pullup_replace_vars() does; + * maybe we should try to fold the two routines together. + */ +Node * +adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, + AppendRelInfo **appinfos) +{ + Node *result; + adjust_appendrel_attrs_context context; + + context.root = root; + context.nappinfos = nappinfos; + context.appinfos = appinfos; + + /* If there's nothing to adjust, don't call this function. */ + Assert(nappinfos >= 1 && appinfos != NULL); + + /* + * Must be prepared to start with a Query or a bare expression tree. + */ + if (node && IsA(node, Query)) + { + Query *newnode; + int cnt; + + newnode = query_tree_mutator((Query *) node, + adjust_appendrel_attrs_mutator, + (void *) &context, + QTW_IGNORE_RC_SUBQUERIES); + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (newnode->resultRelation == appinfo->parent_relid) + { + newnode->resultRelation = appinfo->child_relid; + /* Fix tlist resnos too, if it's inherited UPDATE */ + if (newnode->commandType == CMD_UPDATE) + newnode->targetList = + adjust_inherited_tlist(newnode->targetList, + appinfo); + break; + } + } + + result = (Node *) newnode; + } + else + result = adjust_appendrel_attrs_mutator(node, &context); + + return result; +} + +static Node * +adjust_appendrel_attrs_mutator(Node *node, + adjust_appendrel_attrs_context *context) +{ + AppendRelInfo **appinfos = context->appinfos; + int nappinfos = context->nappinfos; + int cnt; + + if (node == NULL) + return NULL; + if (IsA(node, Var)) + { + Var *var = (Var *) copyObject(node); + AppendRelInfo *appinfo = NULL; + + for (cnt = 0; cnt < nappinfos; cnt++) + { + if (var->varno == appinfos[cnt]->parent_relid) + { + appinfo = appinfos[cnt]; + break; + } + } + + if (var->varlevelsup == 0 && appinfo) + { + var->varno = appinfo->child_relid; + var->varnoold = appinfo->child_relid; + if (var->varattno > 0) + { + Node *newnode; + + if (var->varattno > list_length(appinfo->translated_vars)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + var->varattno, get_rel_name(appinfo->parent_reloid)); + newnode = copyObject(list_nth(appinfo->translated_vars, + var->varattno - 1)); + if (newnode == NULL) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + var->varattno, get_rel_name(appinfo->parent_reloid)); + return newnode; + } + else if (var->varattno == 0) + { + /* + * Whole-row Var: if we are dealing with named rowtypes, we + * can use a whole-row Var for the child table plus a coercion + * step to convert the tuple layout to the parent's rowtype. + * Otherwise we have to generate a RowExpr. + */ + if (OidIsValid(appinfo->child_reltype)) + { + Assert(var->vartype == appinfo->parent_reltype); + if (appinfo->parent_reltype != appinfo->child_reltype) + { + ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr); + + r->arg = (Expr *) var; + r->resulttype = appinfo->parent_reltype; + r->convertformat = COERCE_IMPLICIT_CAST; + r->location = -1; + /* Make sure the Var node has the right type ID, too */ + var->vartype = appinfo->child_reltype; + return (Node *) r; + } + } + else + { + /* + * Build a RowExpr containing the translated variables. + * + * In practice var->vartype will always be RECORDOID here, + * so we need to come up with some suitable column names. + * We use the parent RTE's column names. + * + * Note: we can't get here for inheritance cases, so there + * is no need to worry that translated_vars might contain + * some dummy NULLs. + */ + RowExpr *rowexpr; + List *fields; + RangeTblEntry *rte; + + rte = rt_fetch(appinfo->parent_relid, + context->root->parse->rtable); + fields = copyObject(appinfo->translated_vars); + rowexpr = makeNode(RowExpr); + rowexpr->args = fields; + rowexpr->row_typeid = var->vartype; + rowexpr->row_format = COERCE_IMPLICIT_CAST; + rowexpr->colnames = copyObject(rte->eref->colnames); + rowexpr->location = -1; + + return (Node *) rowexpr; + } + } + /* system attributes don't need any other translation */ + } + return (Node *) var; + } + if (IsA(node, CurrentOfExpr)) + { + CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node); + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (cexpr->cvarno == appinfo->parent_relid) + { + cexpr->cvarno = appinfo->child_relid; + break; + } + } + return (Node *) cexpr; + } + if (IsA(node, RangeTblRef)) + { + RangeTblRef *rtr = (RangeTblRef *) copyObject(node); + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (rtr->rtindex == appinfo->parent_relid) + { + rtr->rtindex = appinfo->child_relid; + break; + } + } + return (Node *) rtr; + } + if (IsA(node, JoinExpr)) + { + /* Copy the JoinExpr node with correct mutation of subnodes */ + JoinExpr *j; + AppendRelInfo *appinfo; + + j = (JoinExpr *) expression_tree_mutator(node, + adjust_appendrel_attrs_mutator, + (void *) context); + /* now fix JoinExpr's rtindex (probably never happens) */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + appinfo = appinfos[cnt]; + + if (j->rtindex == appinfo->parent_relid) + { + j->rtindex = appinfo->child_relid; + break; + } + } + return (Node *) j; + } + if (IsA(node, PlaceHolderVar)) + { + /* Copy the PlaceHolderVar node with correct mutation of subnodes */ + PlaceHolderVar *phv; + + phv = (PlaceHolderVar *) expression_tree_mutator(node, + adjust_appendrel_attrs_mutator, + (void *) context); + /* now fix PlaceHolderVar's relid sets */ + if (phv->phlevelsup == 0) + phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos, + context->appinfos); + return (Node *) phv; + } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + + /* Shouldn't need to handle planner auxiliary nodes here */ + Assert(!IsA(node, AppendRelInfo)); + Assert(!IsA(node, PlaceHolderInfo)); + Assert(!IsA(node, MinMaxAggInfo)); + + /* + * We have to process RestrictInfo nodes specially. (Note: although + * set_append_rel_pathlist will hide RestrictInfos in the parent's + * baserestrictinfo list from us, it doesn't hide those in joininfo.) + */ + if (IsA(node, RestrictInfo)) + { + RestrictInfo *oldinfo = (RestrictInfo *) node; + RestrictInfo *newinfo = makeNode(RestrictInfo); + + /* Copy all flat-copiable fields */ + memcpy(newinfo, oldinfo, sizeof(RestrictInfo)); + + /* Recursively fix the clause itself */ + newinfo->clause = (Expr *) + adjust_appendrel_attrs_mutator((Node *) oldinfo->clause, context); + + /* and the modified version, if an OR clause */ + newinfo->orclause = (Expr *) + adjust_appendrel_attrs_mutator((Node *) oldinfo->orclause, context); + + /* adjust relid sets too */ + newinfo->clause_relids = adjust_child_relids(oldinfo->clause_relids, + context->nappinfos, + context->appinfos); + newinfo->required_relids = adjust_child_relids(oldinfo->required_relids, + context->nappinfos, + context->appinfos); + newinfo->outer_relids = adjust_child_relids(oldinfo->outer_relids, + context->nappinfos, + context->appinfos); + newinfo->nullable_relids = adjust_child_relids(oldinfo->nullable_relids, + context->nappinfos, + context->appinfos); + newinfo->left_relids = adjust_child_relids(oldinfo->left_relids, + context->nappinfos, + context->appinfos); + newinfo->right_relids = adjust_child_relids(oldinfo->right_relids, + context->nappinfos, + context->appinfos); + + /* + * Reset cached derivative fields, since these might need to have + * different values when considering the child relation. Note we + * don't reset left_ec/right_ec: each child variable is implicitly + * equivalent to its parent, so still a member of the same EC if any. + */ + newinfo->eval_cost.startup = -1; + newinfo->norm_selec = -1; + newinfo->outer_selec = -1; + newinfo->left_em = NULL; + newinfo->right_em = NULL; + newinfo->scansel_cache = NIL; + newinfo->left_bucketsize = -1; + newinfo->right_bucketsize = -1; + newinfo->left_mcvfreq = -1; + newinfo->right_mcvfreq = -1; + + return (Node *) newinfo; + } + + /* + * NOTE: we do not need to recurse into sublinks, because they should + * already have been converted to subplans before we see them. + */ + Assert(!IsA(node, SubLink)); + Assert(!IsA(node, Query)); + + return expression_tree_mutator(node, adjust_appendrel_attrs_mutator, + (void *) context); +} + +/* + * Substitute child relids for parent relids in a Relid set. The array of + * appinfos specifies the substitutions to be performed. + */ +static Relids +adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos) +{ + Bitmapset *result = NULL; + int cnt; + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + /* Remove parent, add child */ + if (bms_is_member(appinfo->parent_relid, relids)) + { + /* Make a copy if we are changing the set. */ + if (!result) + result = bms_copy(relids); + + result = bms_del_member(result, appinfo->parent_relid); + result = bms_add_member(result, appinfo->child_relid); + } + } + + /* If we made any changes, return the modified copy. */ + if (result) + return result; + + /* Otherwise, return the original set without modification. */ + return relids; +} + +/* + * Replace any relid present in top_parent_relids with its child in + * child_relids. Members of child_relids can be multiple levels below top + * parent in the partition hierarchy. + */ +Relids +adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, + Relids child_relids, Relids top_parent_relids) +{ + AppendRelInfo **appinfos; + int nappinfos; + Relids parent_relids = NULL; + Relids result; + Relids tmp_result = NULL; + int cnt; + + /* + * If the given relids set doesn't contain any of the top parent relids, + * it will remain unchanged. + */ + if (!bms_overlap(relids, top_parent_relids)) + return relids; + + appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); + + /* Construct relids set for the immediate parent of the given child. */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); + } + + /* Recurse if immediate parent is not the top parent. */ + if (!bms_equal(parent_relids, top_parent_relids)) + { + tmp_result = adjust_child_relids_multilevel(root, relids, + parent_relids, + top_parent_relids); + relids = tmp_result; + } + + result = adjust_child_relids(relids, nappinfos, appinfos); + + /* Free memory consumed by any intermediate result. */ + if (tmp_result) + bms_free(tmp_result); + bms_free(parent_relids); + pfree(appinfos); + + return result; +} + +/* + * adjust_appendrel_attrs_multilevel + * Apply Var translations from a toplevel appendrel parent down to a child. + * + * In some cases we need to translate expressions referencing a parent relation + * to reference an appendrel child that's multiple levels removed from it. + */ +Node * +adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, + Relids child_relids, + Relids top_parent_relids) +{ + AppendRelInfo **appinfos; + Bitmapset *parent_relids = NULL; + int nappinfos; + int cnt; + + Assert(bms_num_members(child_relids) == bms_num_members(top_parent_relids)); + + appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); + + /* Construct relids set for the immediate parent of given child. */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); + } + + /* Recurse if immediate parent is not the top parent. */ + if (!bms_equal(parent_relids, top_parent_relids)) + node = adjust_appendrel_attrs_multilevel(root, node, parent_relids, + top_parent_relids); + + /* Now translate for this child */ + node = adjust_appendrel_attrs(root, node, nappinfos, appinfos); + + pfree(appinfos); + + return node; +} + +/* + * Construct the SpecialJoinInfo for a child-join by translating + * SpecialJoinInfo for the join between parents. left_relids and right_relids + * are the relids of left and right side of the join respectively. + */ +SpecialJoinInfo * +build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, + Relids left_relids, Relids right_relids) +{ + SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo); + AppendRelInfo **left_appinfos; + int left_nappinfos; + AppendRelInfo **right_appinfos; + int right_nappinfos; + + memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo)); + left_appinfos = find_appinfos_by_relids(root, left_relids, + &left_nappinfos); + right_appinfos = find_appinfos_by_relids(root, right_relids, + &right_nappinfos); + + sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand, + left_nappinfos, left_appinfos); + sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand, + right_nappinfos, + right_appinfos); + sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand, + left_nappinfos, left_appinfos); + sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand, + right_nappinfos, + right_appinfos); + sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root, + (Node *) sjinfo->semi_rhs_exprs, + right_nappinfos, + right_appinfos); + + pfree(left_appinfos); + pfree(right_appinfos); + + return sjinfo; +} + +/* + * find_appinfos_by_relids + * Find AppendRelInfo structures for all relations specified by relids. + * + * The AppendRelInfos are returned in an array, which can be pfree'd by the + * caller. *nappinfos is set to the number of entries in the array. + */ +AppendRelInfo ** +find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos) +{ + AppendRelInfo **appinfos; + int cnt = 0; + int i; + + *nappinfos = bms_num_members(relids); + appinfos = (AppendRelInfo **) palloc(sizeof(AppendRelInfo *) * *nappinfos); + + i = -1; + while ((i = bms_next_member(relids, i)) >= 0) + { + AppendRelInfo *appinfo = root->append_rel_array[i]; + + if (!appinfo) + elog(ERROR, "child rel %d not found in append_rel_array", i); + + appinfos[cnt++] = appinfo; + } + return appinfos; +} + +/* + * make_inh_translation_list + * Build the list of translations from parent Vars to child Vars for + * an inheritance child. + * + * For paranoia's sake, we match type/collation as well as attribute name. + */ +static void +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) +{ + List *vars = NIL; + int oldnatts = old_tupdesc->natts; + int newnatts = new_tupdesc->natts; + int old_attno; + int new_attno = 0; + + for (old_attno = 0; old_attno < oldnatts; old_attno++) + { + Form_pg_attribute att; + char *attname; + Oid atttypid; + int32 atttypmod; + Oid attcollation; + + att = TupleDescAttr(old_tupdesc, old_attno); + if (att->attisdropped) + { + /* Just put NULL into this list entry */ + vars = lappend(vars, NULL); + continue; + } + attname = NameStr(att->attname); + atttypid = att->atttypid; + atttypmod = att->atttypmod; + attcollation = att->attcollation; + + /* + * When we are generating the "translation list" for the parent table + * of an inheritance set, no need to search for matches. + */ + if (from_rel == to_rel) + { + vars = lappend(vars, makeVar(newvarno, + (AttrNumber) (old_attno + 1), + atttypid, + atttypmod, + attcollation, + 0)); + continue; + } + + /* + * Otherwise we have to search for the matching column by name. + * There's no guarantee it'll have the same column position, because + * of cases like ALTER TABLE ADD COLUMN and multiple inheritance. + * However, in simple cases, the relative order of columns is mostly + * the same in both relations, so try the column of newrelation that + * follows immediately after the one that we just found, and if that + * fails, let syscache handle it. + */ + if (new_attno >= newnatts || + (att = TupleDescAttr(new_tupdesc, new_attno))->attisdropped || + strcmp(attname, NameStr(att->attname)) != 0) + { + HeapTuple newtup; + + newtup = SearchSysCacheAttName(to_rel, attname); + if (!newtup) + elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", + attname, get_rel_name(to_rel)); + new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; + ReleaseSysCache(newtup); + + att = TupleDescAttr(new_tupdesc, new_attno); + } + + /* Found it, check type and collation match */ + if (atttypid != att->atttypid || atttypmod != att->atttypmod) + elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", + attname, get_rel_name(to_rel)); + if (attcollation != att->attcollation) + elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", + attname, get_rel_name(to_rel)); + + vars = lappend(vars, makeVar(newvarno, + (AttrNumber) (new_attno + 1), + atttypid, + atttypmod, + attcollation, + 0)); + new_attno++; + } + + *translated_vars = vars; +} + +/* + * translate_col_privs + * Translate a bitmapset representing per-column privileges from the + * parent rel's attribute numbering to the child's. + * + * The only surprise here is that we don't translate a parent whole-row + * reference into a child whole-row reference. That would mean requiring + * permissions on all child columns, which is overly strict, since the + * query is really only going to reference the inherited columns. Instead + * we set the per-column bits for all inherited columns. + */ +Bitmapset * +translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars) +{ + Bitmapset *child_privs = NULL; + bool whole_row; + int attno; + ListCell *lc; + + /* System attributes have the same numbers in all tables */ + for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) + { + if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + attno - FirstLowInvalidHeapAttributeNumber); + } + + /* Check if parent has whole-row reference */ + whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, + parent_privs); + + /* And now translate the regular user attributes, using the vars list */ + attno = InvalidAttrNumber; + foreach(lc, translated_vars) + { + Var *var = lfirst_node(Var, lc); + + attno++; + if (var == NULL) /* ignore dropped columns */ + continue; + if (whole_row || + bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + var->varattno - FirstLowInvalidHeapAttributeNumber); + } + + return child_privs; +} + +/* + * Adjust the targetlist entries of an inherited UPDATE operation + * + * The expressions have already been fixed, but we have to make sure that + * the target resnos match the child table (they may not, in the case of + * a column that was added after-the-fact by ALTER TABLE). In some cases + * this can force us to re-order the tlist to preserve resno ordering. + * (We do all this work in special cases so that preptlist.c is fast for + * the typical case.) + * + * The given tlist has already been through expression_tree_mutator; + * therefore the TargetEntry nodes are fresh copies that it's okay to + * scribble on. + * + * Note that this is not needed for INSERT because INSERT isn't inheritable. + */ +static List * +adjust_inherited_tlist(List *tlist, AppendRelInfo *context) +{ + bool changed_it = false; + ListCell *tl; + List *new_tlist; + bool more; + int attrno; + + /* This should only happen for an inheritance case, not UNION ALL */ + Assert(OidIsValid(context->parent_reloid)); + + /* Scan tlist and update resnos to match attnums of child rel */ + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + Var *childvar; + + if (tle->resjunk) + continue; /* ignore junk items */ + + /* Look up the translation of this column: it must be a Var */ + if (tle->resno <= 0 || + tle->resno > list_length(context->translated_vars)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + tle->resno, get_rel_name(context->parent_reloid)); + childvar = (Var *) list_nth(context->translated_vars, tle->resno - 1); + if (childvar == NULL || !IsA(childvar, Var)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + tle->resno, get_rel_name(context->parent_reloid)); + + if (tle->resno != childvar->varattno) + { + tle->resno = childvar->varattno; + changed_it = true; + } + } + + /* + * If we changed anything, re-sort the tlist by resno, and make sure + * resjunk entries have resnos above the last real resno. The sort + * algorithm is a bit stupid, but for such a seldom-taken path, small is + * probably better than fast. + */ + if (!changed_it) + return tlist; + + new_tlist = NIL; + more = true; + for (attrno = 1; more; attrno++) + { + more = false; + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + + if (tle->resjunk) + continue; /* ignore junk items */ + + if (tle->resno == attrno) + new_tlist = lappend(new_tlist, tle); + else if (tle->resno > attrno) + more = true; + } + } + + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + + if (!tle->resjunk) + continue; /* here, ignore non-junk items */ + + tle->resno = attrno; + new_tlist = lappend(new_tlist, tle); + attrno++; + } + + return new_tlist; +} diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index d50d86b252..8ce88876c4 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -20,6 +20,7 @@ #include "foreign/fdwapi.h" #include "nodes/extensible.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index c23db9d78d..52a11f434f 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -17,6 +17,7 @@ #include <limits.h> #include "miscadmin.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index fcf8d6032c..e76906da1f 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -44,6 +44,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/include/optimizer/append.h b/src/include/optimizer/append.h new file mode 100644 index 0000000000..b5f025c137 --- /dev/null +++ b/src/include/optimizer/append.h @@ -0,0 +1,25 @@ +/*------------------------------------------------------------------------- + * + * append.h + * prototypes for append.c. + * + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/optimizer/append.h + * + *------------------------------------------------------------------------- + */ +#ifndef APPEND_H +#define APPEND_H + +#include "nodes/relation.h" + +/* + * append.c + * utilities for dealing with append relations + */ +extern void expand_inherited_tables(PlannerInfo *root); + +#endif /* APPEND_H */ diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h new file mode 100644 index 0000000000..e205e78e6d --- /dev/null +++ b/src/include/optimizer/appendinfo.h @@ -0,0 +1,43 @@ +/*------------------------------------------------------------------------- + * + * appendinfo.h + * Routines for mapping expressions between append rel parent(s) and + * children + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/optimizer/appendinfo.h + * + *------------------------------------------------------------------------- + */ +#ifndef APPENDINFO_H +#define APPENDINFO_H + +#include "nodes/plannodes.h" +#include "nodes/relation.h" +#include "utils/relcache.h" + +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parentrel, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex); +extern Bitmapset *translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars); +extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, + int nappinfos, AppendRelInfo **appinfos); + +extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, + Relids child_relids, + Relids top_parent_relids); + +extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root, + Relids relids, int *nappinfos); + +extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, + SpecialJoinInfo *parent_sjinfo, + Relids left_relids, Relids right_relids); +extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, + Relids child_relids, Relids top_parent_relids); + +#endif /* APPENDINFO_H */ diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index faae07d240..3ad9b4a77e 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -48,24 +48,5 @@ extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); */ extern RelOptInfo *plan_set_operations(PlannerInfo *root); -extern void expand_inherited_tables(PlannerInfo *root); -extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, - int nappinfos, AppendRelInfo **appinfos); -extern AppendRelInfo *make_append_rel_info(RelOptInfo *parentrel, - RangeTblEntry *parentrte, - TupleDesc childdesc, Oid childoid, Oid childreltype, - Index childRTindex); -extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, - Relids child_relids, - Relids top_parent_relids); - -extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root, - Relids relids, int *nappinfos); - -extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, - SpecialJoinInfo *parent_sjinfo, - Relids left_relids, Relids right_relids); -extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, - Relids child_relids, Relids top_parent_relids); #endif /* PREP_H */ -- 2.11.0
v9-0005-Teach-planner-to-only-process-unpruned-partitions.patch (text/plain, 6.9K) download From 878c3f75b38a2f89281a96beed37f1e4e1ea5f69 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 15 Oct 2018 10:59:24 +0900 Subject: [PATCH v9 5/6] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/append.c | 1 + src/backend/optimizer/util/relnode.c | 3 +++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/relation.h | 4 ++++ 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 6e321ec9e7..76531a05cf 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1424,6 +1424,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 62ad5eb5c2..42f8a92240 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6757,7 +6757,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -6765,9 +6767,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -6848,7 +6848,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -6860,7 +6859,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -6870,9 +6871,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/append.c b/src/backend/optimizer/util/append.c index e78a61d613..7227eed4c4 100644 --- a/src/backend/optimizer/util/append.c +++ b/src/backend/optimizer/util/append.c @@ -350,6 +350,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, /* Perform pruning. */ partindexes = prune_append_rel_partitions(parentrel); + parentrel->live_parts = partindexes; /* Must expand PlannerInfo arrays before we can add children. */ expand_planner_arrays(root, bms_num_members(partindexes)); diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 52a11f434f..6c7eafe6db 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1748,6 +1748,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index e76906da1f..8bc2f5c5c2 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -441,29 +441,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index e6fdbcd030..ff9a3fde86 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -727,6 +727,10 @@ typedef struct RelOptInfo Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Indexes into part_rels of the non-NULL + * RelOptInfos of unpruned partitions; exists + * to avoid having to iterate over the entire + * part_rels array to filter NULL entries. */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.11.0
v9-0006-Do-not-lock-all-partitions-at-the-beginning.patch (text/plain, 1.8K) download From e1f37f4c5c1ec7fcf1d91f47fd615d4ea61a2a46 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 12 Oct 2018 19:12:53 +0900 Subject: [PATCH v9 6/6] Do not lock all partitions at the beginning --- src/backend/optimizer/util/append.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/backend/optimizer/util/append.c b/src/backend/optimizer/util/append.c index 7227eed4c4..9b3fc41a1e 100644 --- a/src/backend/optimizer/util/append.c +++ b/src/backend/optimizer/util/append.c @@ -317,10 +317,6 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, * * Partitions are added to the query in order in which they are found in * the parent's PartitionDesc. - * - * Note: even though only the unpruned partitions will be added to the - * resulting plan, this still locks *all* partitions via find_all_inheritors - * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, @@ -334,10 +330,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, parentrel->inh_root_parent : parentRTindex; - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - /* * Initialize partitioned_child_rels to contain this RT index. * @@ -373,8 +365,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, RangeTblEntry *childrte; Index childRTindex; - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); + newrelation = heap_open(childOID, lockmode); Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2018-12-07 00:57  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 1 reply; 265+ messages in thread

From: Imai, Yoshikazu @ 2018-12-07 00:57 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Hi, Amit

Thanks for the quick modification.

On Wed, Dec 5, 2018 at 8:26 PM, Amit Langote wrote:
> > 1.
...
> > 5.
> > 0003: line 1620-1621
> > 
> > + * After creating the RelOptInfo for the given child RT index, it goes on to
> > + * initialize some of its fields base on the parent RelOptInfo.
> > 
> > s/fields base on/fields based on/
> 
> Fixed all of 1-5.

Thanks for fixing.

> > 6.
> > parsenodes.h
> >  906  *    inh is true for relation references that should be expanded to include
> >  907  *    inheritance children, if the rel has any.  This *must* be false for
> >  908  *    RTEs other than RTE_RELATION entries.
> > 
> > I think inh can become true now even if RTEKind equals RTE_SUBQUERY, so latter
> > sentence need to be modified.
> 
> 
> 
> Seems like an existing comment bug.  Why don't you send a patch as you
> discovered it? :)

Thanks, I am pleased with your proposal. I'll post it as a small fix of the comment.

> > 7.
> > 0005: line 109-115
... 
> Un-pruned partitions may become dummy due to contradictory constraints or
> constraint exclusion using normal CHECK constraints later and whether it's
> dummy is checked properly by functions that iterate over live_parts.

Ah, I understand partitions are eliminated contradictory constraints or
constraint exclusion, both using constraints.

> Attached updated patches.  I have a few other changes in mind to make to
> 0001 such that the range table in each child's version of Query contains
> only that child table in place of the original target relation, instead of
> *all* child tables which is the current behavior.  The current behavior
> makes range_table_mutator a big bottleneck when the number of un-pruned
> target children is large.  But I'm saving it for the next week so that I

OK. I will continue the review of 0001 before/after your supplying of next
patch with keeping those in mind.

> can prepare for the PGConf.ASIA that's starting on Monday next week.  See
> you there. :)

Yeah, see you there. :)


--
Yoshikazu Imai



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2018-12-25 07:47  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  0 siblings, 1 reply; 265+ messages in thread

From: Imai, Yoshikazu @ 2018-12-25 07:47 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Hi, Amit,

On Fri, Dec 7, 2018 at 0:57 AM, Imai, Yoshikazu wrote:
> OK. I will continue the review of 0001 before/after your supplying of
> next patch with keeping those in mind.

Here's the continuation of the review. Almost all of below comments are
little fixes.

---
0001: line 76-77
In commit message:
  exclusion for target child relation, which is no longer
  is no longer needed.  Constraint exclusion runs during query_planner

s/which is no longer is no longer needed/which is no longer needed/

---
0001: line 464
+	if (IS_DUMMY_REL(find_base_rel(root, resultRelation )))

s/resultRelation )))/resultRelation)))/
(There is an extra space.)

---
0001: line 395-398
+	 * Reset inh_target_child_roots to not be same as parent root's so that
+	 * the subroots for this child's own children (if any) don't end up in
+	 * root parent's list.  We'll eventually merge all entries into one list,
+	 * but that's now now.

s/that's now now/that's not now/

---
0001: line 794
+	 * are put into  a list that will be controlled by a single ModifyTable

s/are put into  a list/are put into a list/
(There are two spaces between "into" and "a".)

---
0001: line 241-242, 253-254, 291-294 (In set_append_rel_size())

+		if (appinfo->parent_relid == root->parse->resultRelation)
+			subroot = adjust_inherit_target_child(root, childrel, appinfo);

+			add_child_rel_equivalences(subroot, appinfo, rel, childrel,
+									   root != subroot);

+		if (subroot != root)
+		{
+			root->inh_target_child_roots =
+							lappend(root->inh_target_child_roots, subroot);

A boolean value of "appinfo->parent_relid == root->parse->resultRelation" is
same with "subroot != root"(because of line 241-242), so we can use bool
variable here like
child_is_target = (appinfo->parent_relid == root->parse->resultRelation).
The name of child_is_target is brought from arguments of
add_child_rel_equivalences() in your patch.

I attached a little diff as "v9-0001-delta.diff".

---
0001: line 313-431

adjust_inherit_target_child() is in allpaths.c in your patch, but it has the
code like below ones which are in master's(not patch applied) planner.c or
planmain.c, so could it be possible in planner.c(or planmain.c)?
This point is less important, but I was just wondering whether 
adjust_inherit_target_child() should be in allpaths.c, planner.c or planmain.c.

+	/* Translate the original query's expressions to this child. */
+	subroot = makeNode(PlannerInfo);
+	memcpy(subroot, root, sizeof(PlannerInfo));

+	root->parse->targetList = root->unexpanded_tlist;
+	subroot->parse = (Query *) adjust_appendrel_attrs(root,
+													  (Node *) root->parse,
+													  1, &appinfo);

+	tlist = preprocess_targetlist(subroot);
+	subroot->processed_tlist = tlist;
+	build_base_rel_tlists(subroot, tlist);

---
0001: line 57-70

In commit message:
  This removes some existing code in inheritance_planner that dealt
  with any subquery RTEs in the query.  The rationale of that code
  was that the subquery RTEs may change during each iteration of
  planning (that is, for different children), so different iterations
  better use different copies of those RTEs.  
  ...
  Since with the new code
  we perform planning just once, I think we don't need this special
  handling.

0001: line 772-782
-	 * controlled by a single ModifyTable node.  All the instances share the
-	 * same rangetable, but each instance must have its own set of subquery
-	 * RTEs within the finished rangetable because (1) they are likely to get
-	 * scribbled on during planning, and (2) it's not inconceivable that
-	 * subqueries could get planned differently in different cases.  We need
-	 * not create duplicate copies of other RTE kinds, in particular not the
-	 * target relations, because they don't have either of those issues.  Not
-	 * having to duplicate the target relations is important because doing so
-	 * (1) would result in a rangetable of length O(N^2) for N targets, with
-	 * at least O(N^3) work expended here; and (2) would greatly complicate
-	 * management of the rowMarks list.

I used considerable time to confirm there are no needs copying subquery RTEs in
the new code, but so far I couldn't. If copied RTEs are only used when planning,
it might not need to copy RTEs in the new code because we perform planning just
once, so I tried to detect when copied RTEs are used or overwritten, but I gave
up.

Of course, there are comments about this,

-	 * same rangetable, but each instance must have its own set of subquery
-	 * RTEs within the finished rangetable because (1) they are likely to get
-	 * scribbled on during planning, and (2) it's not inconceivable that

so copied RTEs might be used when planning, but I couldn't find the actual codes.
I also checked commits[1, 2] related to these codes. I'll check these for more
time but it would be better there are other's review and I also want a help here.

---
Maybe I checked all the way of the v9 patch excluding the codes about
EquivalenceClass codes(0001: line 567-638).
I'll consider whether there are any performance degration case, but I have
no idea for now. Do you have any points you concerns? If there, I'll check it.


[1] https://github.com/postgres/postgres/commit/b3aaf9081a1a95c245fd605dcf02c91b3a5c3a29
[2] https://github.com/postgres/postgres/commit/c03ad5602f529787968fa3201b35c119bbc6d782

--
Yoshikazu Imai



Attachments:

v9-0001-delta.diff (application/octet-stream, 1.6K) download diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 12bb61f..0222485 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -1018,6 +1018,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, ListCell *parentvars; ListCell *childvars; PlannerInfo *subroot = root; + bool child_is_target = + (appinfo->parent_relid == root->parse->resultRelation); /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1071,7 +1073,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, * child relation that will be suitable to use the child also as the * target relation. */ - if (appinfo->parent_relid == root->parse->resultRelation) + if (child_is_target) subroot = adjust_inherit_target_child(root, childrel, appinfo); /* @@ -1087,7 +1089,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, */ if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) add_child_rel_equivalences(subroot, appinfo, rel, childrel, - root != subroot); + child_is_target); childrel->has_eclass_joins = rel->has_eclass_joins; /* CE failed, so finish copying/modifying join quals. */ @@ -1139,7 +1141,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, * If we modified subroot for the target inheritance case, add it * to root->inh_target_child_roots. */ - if (subroot != root) + if (child_is_target) { root->inh_target_child_roots = lappend(root->inh_target_child_roots, subroot);
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-12-26 04:28  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  0 siblings, 1 reply; 265+ messages in thread

From: Amit Langote @ 2018-12-26 04:28 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: 'David Rowley' <david.rowley@2ndquadrant.com>; 'Alvaro Herrera' <alvherre@2ndquadrant.com>; pgsql-hackers

Thank you Imai-san.

On 2018/12/25 16:47, Imai, Yoshikazu wrote:
> Here's the continuation of the review. Almost all of below comments are
> little fixes.
> 
> ---
> 0001: line 76-77
> In commit message:
>   exclusion for target child relation, which is no longer
>   is no longer needed.  Constraint exclusion runs during query_planner
> 
> s/which is no longer is no longer needed/which is no longer needed/
> 
> ---
> 0001: line 464
> +	if (IS_DUMMY_REL(find_base_rel(root, resultRelation )))
> 
> s/resultRelation )))/resultRelation)))/
> (There is an extra space.)
> 
> ---
> 0001: line 395-398
> +	 * Reset inh_target_child_roots to not be same as parent root's so that
> +	 * the subroots for this child's own children (if any) don't end up in
> +	 * root parent's list.  We'll eventually merge all entries into one list,
> +	 * but that's now now.
> 
> s/that's now now/that's not now/
> 
> ---
> 0001: line 794
> +	 * are put into  a list that will be controlled by a single ModifyTable
> 
> s/are put into  a list/are put into a list/
> (There are two spaces between "into" and "a".)

All fixed in my local repository.

> ---
> 0001: line 241-242, 253-254, 291-294 (In set_append_rel_size())
> 
> +		if (appinfo->parent_relid == root->parse->resultRelation)
> +			subroot = adjust_inherit_target_child(root, childrel, appinfo);
> 
> +			add_child_rel_equivalences(subroot, appinfo, rel, childrel,
> +									   root != subroot);
> 
> +		if (subroot != root)
> +		{
> +			root->inh_target_child_roots =
> +							lappend(root->inh_target_child_roots, subroot);
> 
> A boolean value of "appinfo->parent_relid == root->parse->resultRelation" is
> same with "subroot != root"(because of line 241-242), so we can use bool
> variable here like
> child_is_target = (appinfo->parent_relid == root->parse->resultRelation).
> The name of child_is_target is brought from arguments of
> add_child_rel_equivalences() in your patch.
>
> I attached a little diff as "v9-0001-delta.diff".

Sounds like a good idea for clarifying the code, so done.

> ---
> 0001: line 313-431
> 
> adjust_inherit_target_child() is in allpaths.c in your patch, but it has the
> code like below ones which are in master's(not patch applied) planner.c or
> planmain.c, so could it be possible in planner.c(or planmain.c)?
> This point is less important, but I was just wondering whether 
> adjust_inherit_target_child() should be in allpaths.c, planner.c or planmain.c.
> 
> +	/* Translate the original query's expressions to this child. */
> +	subroot = makeNode(PlannerInfo);
> +	memcpy(subroot, root, sizeof(PlannerInfo));
> 
> +	root->parse->targetList = root->unexpanded_tlist;
> +	subroot->parse = (Query *) adjust_appendrel_attrs(root,
> +													  (Node *) root->parse,
> +													  1, &appinfo);
> 
> +	tlist = preprocess_targetlist(subroot);
> +	subroot->processed_tlist = tlist;
> +	build_base_rel_tlists(subroot, tlist);

I'm thinking of changing where adjust_inherit_target_child gets called
from.  In the current patch, it's in the middle of set_rel_size which I'm
starting to think is a not a good place for it.  Maybe, I'll place the
call call near to where inheritance is expanded.

> ---
> 0001: line 57-70
> 
> In commit message:
>   This removes some existing code in inheritance_planner that dealt
>   with any subquery RTEs in the query.  The rationale of that code
>   was that the subquery RTEs may change during each iteration of
>   planning (that is, for different children), so different iterations
>   better use different copies of those RTEs.  
>   ...
>   Since with the new code
>   we perform planning just once, I think we don't need this special
>   handling.
> 
> 0001: line 772-782
> -	 * controlled by a single ModifyTable node.  All the instances share the
> -	 * same rangetable, but each instance must have its own set of subquery
> -	 * RTEs within the finished rangetable because (1) they are likely to get
> -	 * scribbled on during planning, and (2) it's not inconceivable that
> -	 * subqueries could get planned differently in different cases.  We need
> -	 * not create duplicate copies of other RTE kinds, in particular not the
> -	 * target relations, because they don't have either of those issues.  Not
> -	 * having to duplicate the target relations is important because doing so
> -	 * (1) would result in a rangetable of length O(N^2) for N targets, with
> -	 * at least O(N^3) work expended here; and (2) would greatly complicate
> -	 * management of the rowMarks list.
> 
> I used considerable time to confirm there are no needs copying subquery RTEs in
> the new code, but so far I couldn't. If copied RTEs are only used when planning,
> it might not need to copy RTEs in the new code because we perform planning just
> once, so I tried to detect when copied RTEs are used or overwritten, but I gave
> up.
> 
> Of course, there are comments about this,
> 
> -	 * same rangetable, but each instance must have its own set of subquery
> -	 * RTEs within the finished rangetable because (1) they are likely to get
> -	 * scribbled on during planning, and (2) it's not inconceivable that
> 
> so copied RTEs might be used when planning, but I couldn't find the actual codes.
> I also checked commits[1, 2] related to these codes. I'll check these for more
> time but it would be better there are other's review and I also want a help here.
>
> [1]
https://github.com/postgres/postgres/commit/b3aaf9081a1a95c245fd605dcf02c91b3a5c3a29
> [2]
https://github.com/postgres/postgres/commit/c03ad5602f529787968fa3201b35c119bbc6d782

Thank you very much for spending time on that.  I'd really like someone
else to consider this as well.

Here's the summary of what I'm proposing to change with respect to the
above: because we perform scan-level planning only once for any given
relation including for sub-queries with the patch applied, we no longer
need to make copies of sub-query RTEs that are currently needed due to
repeated planning, with one copy per child target relation.  Since there
are no new copies, there is no need to process various nodes to change the
RT index being used to refer to the sub-query.  I have removed the code
that does to copying of subquery RTEs and the code that does translation
due new RT index being assigned every time a new copy was being made.

> ---
> Maybe I checked all the way of the v9 patch excluding the codes about
> EquivalenceClass codes(0001: line 567-638).
> I'll consider whether there are any performance degration case, but I have
> no idea for now. Do you have any points you concerns? If there, I'll check it.

I will send an updated patch hopefully before my new year vacation that
starts on Friday this week.

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2018-12-27 11:25  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 2 replies; 265+ messages in thread

From: Amit Langote @ 2018-12-27 11:25 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: 'David Rowley' <david.rowley@2ndquadrant.com>; 'Alvaro Herrera' <alvherre@2ndquadrant.com>; pgsql-hackers

Hi,

On 2018/12/26 13:28, Amit Langote wrote:
> On 2018/12/25 16:47, Imai, Yoshikazu wrote:
>> adjust_inherit_target_child() is in allpaths.c in your patch, but it has the
>> code like below ones which are in master's(not patch applied) planner.c or
>> planmain.c, so could it be possible in planner.c(or planmain.c)?
>> This point is less important, but I was just wondering whether 
>> adjust_inherit_target_child() should be in allpaths.c, planner.c or planmain.c.
>>
>> +	/* Translate the original query's expressions to this child. */
>> +	subroot = makeNode(PlannerInfo);
>> +	memcpy(subroot, root, sizeof(PlannerInfo));
>>
>> +	root->parse->targetList = root->unexpanded_tlist;
>> +	subroot->parse = (Query *) adjust_appendrel_attrs(root,
>> +													  (Node *) root->parse,
>> +													  1, &appinfo);
>>
>> +	tlist = preprocess_targetlist(subroot);
>> +	subroot->processed_tlist = tlist;
>> +	build_base_rel_tlists(subroot, tlist);
> 
> I'm thinking of changing where adjust_inherit_target_child gets called
> from.  In the current patch, it's in the middle of set_rel_size which I'm
> starting to think is a not a good place for it.  Maybe, I'll place the
> call call near to where inheritance is expanded.

So what I did here is that I added a new step to query_planner() itself
that adds the PlannerInfos for inheritance child target relations and
hence moved the function adjust_inherit_target_child() to planmain.c and
renamed it to add_inherit_target_child_root().  I've removed its
RelOptInfo argument and decided to modify the child RelOptInfo's
targetlist in allpaths.c.  So, add_inherit_target_child_root() only builds
the child PlannerInfo now.  Child PlannerInfo's are now stored in an array
of simple_rel_array_size elements, instead of a list.  It's filled with
NULLs except for the slots corresponding to child target relations.

In allpaths.c, I've added set_inherit_target_rel_sizes and
set_inherit_target_rel_pathlists, which look like set_append_rel_size and
set_append_rel_pathlist, respectively.  The reason to add separate
functions for the target relation case is that we don't want to combine
the outputs of children to form an "append relation" in that case.  So,
this patch no longer modifies set_append_rel_size for handling the case
where the parent relation is query's target relation.

> I will send an updated patch hopefully before my new year vacation that
> starts on Friday this week.

Here's the v10 of the patch.  I didn't get chance to do more changes than
those described above and address Imai-san's review comments yesterday.

Have a wonderful new year!  I'll be back on January 7 to reply on this thread.

Thanks,
Amit

Attachments: download all (6 files, .tar.gz)

v10-0001-Overhaul-inheritance-update-delete-planning.patch (text/plain, 71.2K) download From 96827948f257e4221997b84d4087804b4e5e34e2 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 11:18:12 +0900 Subject: [PATCH v10 1/6] Overhaul inheritance update/delete planning With the current method, inheritance_planner, which handles the planning for update/delete commands targeting inheritance trees, performs the *whole* planning for each child table. That involves translating the query tree to set a given child table in place of the original inheritance root table as the query's target table, followed by calling grouping_planner on the translated query, which performs query planning and additional steps needed to apply correct target list based on the child target table. That's inefficient for two reasons: 1. partprune.c cannot be used to perform partition pruning, because it can only be invoked from query_planner. With the current approach, query_planner only sees the individual partitions in the query tree it receives, not the parent partitioned table. This leaves each partition to be pruned using constraint exclusion. (constraint exclusion cannon prune hash partitions which means UPDATE/DELETE don't support hash partition pruning.) 2. Repeated invocation of query_planner results in allocating large amounts of memory, especially if there are many child tables. Also, it involves repeatedly performing the same processing, such as jointree processing. This commit addresses both of the issues by rewriting portions of inheritance_planner and adding some supporting functionality to allpath.c. With the new implementation, inheritance_planner calls query_planner only once at the beginning with the original unmodified query, which creates the access paths of individual relations after pruning any unnecessary partitions. For each target child relation, it also creates a sub-PartitionInfo containing translated version of the query and a targetlist suitable for the child. Child PlannerInfos are saved in the top PlannerInfo for using later. If the query involves join against the target relation, join paths are created for each target child relation by replacing the original target table in the join tree by a given child table. Join relations (RelOptInfos thereof) for all target child relations are collected in a global list in the top PlannerInfo. After creating the join paths for all target child relations, inheritance_planner calls grouping_planner() on each child join relation using the previously created child PlannerInfo to finish up the paths such that they produce query's top-level target list expanded according to a given child relation's descriptor. grouping_planner()'s interface is modified so that we can pass it what's called a 'planned_rel', a RelOptInfo that already contains the necessary paths needed to produce its output. This removes some existing code in inheritance_planner that dealt with any subquery RTEs in the query. The rationale of that code was that the subquery RTEs may change during each iteration of planning (that is, for different children), so different iterations better use different copies of those RTEs. That was handled by making fresh copies of those RTEs for each iteration of planning which were appended to the range table, accompanied by modifying all expressions and auxiliary structures that referenced the original subquery RTEs to instead reference the copies (that is, change the varnos). This copying meant we would end up adding S * N new entries to the original range table by the time we got to the last unpruned child, where S is the number of subquery RTEs in the original query and N the number of unpruned children. Since with the new code we perform planning just once, I think we don't need this special handling. Actually, there is a regression test output change due to no longer having copies of subquery RTEs (see the diff of partition_join.out file.) Also we no longer need the hack in relation_excluded_by_constraints() involving setting PlannerInfo.inhTargetKind to enable constraint exclusion for target child relation, which is no longer needed. Constraint exclusion now runs during query_planner step described above. Regression test output change in partition_join.out is due to the fact that we no longer create duplicates of subquery RTEs in child range tables. --- doc/src/sgml/ddl.sgml | 14 +- src/backend/nodes/outfuncs.c | 1 - src/backend/optimizer/path/allpaths.c | 602 +++++++++++++++++++++++++-- src/backend/optimizer/path/equivclass.c | 27 +- src/backend/optimizer/plan/createplan.c | 10 - src/backend/optimizer/plan/planmain.c | 166 +++++++- src/backend/optimizer/plan/planner.c | 434 +++++-------------- src/backend/optimizer/prep/prepjointree.c | 1 - src/backend/optimizer/prep/prepunion.c | 33 +- src/backend/optimizer/util/plancat.c | 50 +-- src/include/nodes/relation.h | 34 +- src/include/optimizer/paths.h | 3 +- src/test/regress/expected/partition_join.out | 4 +- 13 files changed, 937 insertions(+), 442 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 85e4358988..0333f673b3 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -4439,15 +4439,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <note> <para> - Currently, pruning of partitions during the planning of an - <command>UPDATE</command> or <command>DELETE</command> command is - implemented using the constraint exclusion method (however, it is - controlled by the <literal>enable_partition_pruning</literal> rather than - <literal>constraint_exclusion</literal>) &mdash; see the following section - for details and caveats that apply. - </para> - - <para> Execution-time partition pruning currently only occurs for the <literal>Append</literal> and <literal>MergeAppend</literal> node types. It is not yet implemented for the <literal>ModifyTable</literal> node @@ -4470,9 +4461,8 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <para> <firstterm>Constraint exclusion</firstterm> is a query optimization - technique similar to partition pruning. While it is primarily used - for partitioning implemented using the legacy inheritance method, it can be - used for other purposes, including with declarative partitioning. + technique similar to partition pruning. It is primarily used + for partitioning implemented using the legacy inheritance method. </para> <para> diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index be6b4ca2f4..3c9db22507 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2210,7 +2210,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); WRITE_UINT_FIELD(qual_security_level); - WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind); WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasDeletedRTEs); diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 738bb30848..3f3bfbd696 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -36,6 +36,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" @@ -91,8 +92,12 @@ static void set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); +static void set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte); static void set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); +static void set_inherit_target_rel_pathlists(PlannerInfo *root, + RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, @@ -119,6 +124,8 @@ static void set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); +static void inheritance_make_rel_from_joinlist(PlannerInfo *root, + List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, pushdown_safety_info *safetyInfo); static bool recurse_pushdown_safe(Node *setOp, Query *topquery, @@ -149,27 +156,6 @@ make_one_rel(PlannerInfo *root, List *joinlist) Index rti; double total_pages; - /* - * Construct the all_baserels Relids set. - */ - root->all_baserels = NULL; - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - - /* there may be empty slots corresponding to non-baserel RTEs */ - if (brel == NULL) - continue; - - Assert(brel->relid == rti); /* sanity check on array */ - - /* ignore RTEs that are "other rels" */ - if (brel->reloptkind != RELOPT_BASEREL) - continue; - - root->all_baserels = bms_add_member(root->all_baserels, brel->relid); - } - /* Mark base rels as to whether we care about fast-start plans */ set_base_rel_consider_startup(root); @@ -217,13 +203,34 @@ make_one_rel(PlannerInfo *root, List *joinlist) /* * Generate access paths for the entire join tree. + * + * For UPDATE/DELETE on an inheritance parent, join paths should be + * generated for each child result rel separately. */ - rel = make_rel_from_joinlist(root, joinlist); + if (root->parse->resultRelation && + root->simple_rte_array[root->parse->resultRelation]->inh) + { + inheritance_make_rel_from_joinlist(root, joinlist); - /* - * The result should join all and only the query's base rels. - */ - Assert(bms_equal(rel->relids, root->all_baserels)); + /* + * Return the RelOptInfo of original target relation, although this + * doesn't really contain the final path. inheritance_planner + * from where we got here will generate the final path, but it will + * do so by iterative over child subroots, not through this + * RelOptInfo. + */ + rel = find_base_rel(root, root->parse->resultRelation); + } + else + { + rel = make_rel_from_joinlist(root, joinlist); + + /* + * The result should join all and only the query's base rels. + */ + Assert(bms_equal(rel->relids, root->all_baserels)); + + } return rel; } @@ -373,8 +380,15 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_size(root, rel, rti, rte); + /* + * If it's a target relation, set the sizes of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (rti == root->parse->resultRelation) + set_inherit_target_rel_sizes(root, rel, rti, rte); + else + set_append_rel_size(root, rel, rti, rte); } else { @@ -458,14 +472,26 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { + bool inherited_update = false; + if (IS_DUMMY_REL(rel)) { /* We already proved the relation empty, so nothing more to do */ } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_pathlist(root, rel, rti, rte); + /* + * If it's a target relation, set the pathlists of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (rti == root->parse->resultRelation) + { + inherited_update = true; + set_inherit_target_rel_pathlists(root, rel, rti, rte); + } + else + set_append_rel_pathlist(root, rel, rti, rte); } else { @@ -540,8 +566,12 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, if (set_rel_pathlist_hook) (*set_rel_pathlist_hook) (root, rel, rti, rte); - /* Now find the cheapest of the paths for this rel */ - set_cheapest(rel); + /* + * Now find the cheapest of the paths for this rel, unless it's an + * inheritance parent and this is an update/delete operation. + */ + if (!inherited_update) + set_cheapest(rel); #ifdef OPTIMIZER_DEBUG debug_print_rel(root, rel); @@ -903,6 +933,321 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) } /* + * set_inherit_target_rel_sizes + * Set size estimates for the child target relations + * + * The passed-in rel represents the target relation of the query that is + * known to have inheritance children. This is very much like + * set_append_rel_size, except it doesn't set the size estimates for the + * passed-in rel itself, because we don't need to "append" the children + * in this case. + */ +static void +set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + ListCell *l; + bool has_live_children; + Relids live_children = NULL; + bool did_pruning = false; + + /* Guard against stack overflow due to overly deep inheritance tree. */ + check_stack_depth(); + + Assert(IS_SIMPLE_REL(rel)); + + /* + * If the partitioned relation has any baserestrictinfo quals then we + * attempt to use these quals to prune away partitions that cannot + * possibly contain any tuples matching these quals. In this case we'll + * store the relids of all partitions which could possibly contain a + * matching tuple, and skip anything else in the loop below. + */ + if (enable_partition_pruning && + rte->relkind == RELKIND_PARTITIONED_TABLE && + rel->baserestrictinfo != NIL) + { + live_children = prune_append_rel_partitions(rel); + did_pruning = true; + } + + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + List *childquals; + Index cq_min_security; + bool have_const_false_cq; + PlannerInfo *subroot; + ListCell *lc; + List *translated_exprs, + *child_target_exprs; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + subroot = root->inh_target_child_roots[childRTindex]; + + /* + * The child rel's RelOptInfo was already created during + * add_base_rels_to_query. + */ + childrel = find_base_rel(root, childRTindex); + Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = NIL; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, + (Node *) rinfo->clause, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + /* track minimum security level among child quals */ + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might have + * securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals, see expand_inherited_rtentry().) Pull any such + * securityQuals up into the baserestrictinfo for the child. This is + * similar to process_security_barrier_quals() for the parent rel, + * except that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* not likely that we'd see constants here, so no check */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, false, false, + security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* + * OK, we've got all the baserestrictinfo quals for this child. + */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) + { + /* This partition was pruned; skip it. */ + set_dummy_rel_pathlist(childrel); + continue; + } + + if (relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* + * This child need not be scanned, so we can omit it from the + * appendrel. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Add missing Vars to child's reltarget. + * + * add_inherit_target_child_root() would've added only those that are + * needed to be present in the top-level tlist (or ones that + * preprocess_targetlist thinks are needed to be in the tlist.) We + * may need other attributes such as those contained in WHERE clauses, + * which are already computed for the parent during + * deconstruct_jointree processing of the original query (child's + * query never goes through deconstruct_jointree.) + */ + translated_exprs = (List *) + adjust_appendrel_attrs(root, + (Node *) rel->reltarget->exprs, + 1, &appinfo); + child_target_exprs = childrel->reltarget->exprs; + foreach(lc, translated_exprs) + { + Expr *expr = lfirst(lc); + + if (!list_member(child_target_exprs, expr)) + child_target_exprs = lappend(child_target_exprs, expr); + } + + /* + * We have to make child entries in the EquivalenceClass data + * structures as well. This is needed either if the parent + * participates in some eclass joins (because we will want to consider + * inner-indexscan joins on the individual children) or if the parent + * has useful pathkeys (because we should try to build MergeAppend + * paths that produce those sort orderings). Even if this child is + * deemed dummy, it may fall on nullable side in a child-join, which + * in turn may participate in a MergeAppend, where we will need the + * EquivalenceClass data structures. + */ + if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) + add_child_rel_equivalences(subroot, appinfo, rel, childrel, true); + childrel->has_eclass_joins = rel->has_eclass_joins; + + /* CE failed, so finish copying/modifying join quals. */ + childrel->joininfo = (List *) + adjust_appendrel_attrs(subroot, + (Node *) rel->joininfo, + 1, &appinfo); + + /* + * If parallelism is allowable for this query in general, see whether + * it's allowable for this childrel in particular. But if we've + * already decided the appendrel is not parallel-safe as a whole, + * there's no point in considering parallelism for this child. For + * consistency, do this before calling set_rel_size() for the child. + */ + if (root->glob->parallelModeOK && rel->consider_parallel) + set_rel_consider_parallel(subroot, childrel, childRTE); + + /* + * Compute the child's size using possibly modified subroot. + */ + set_rel_size(subroot, childrel, childRTindex, childRTE); + + /* + * It is possible that constraint exclusion detected a contradiction + * within a child subquery, even though we didn't prove one above. If + * so, we can skip this child. + */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* + * If any live child is not parallel-safe, treat the whole appendrel + * as not parallel-safe. In future we might be able to generate plans + * in which some children are farmed out to workers while others are + * not; but we don't have that today, so it's a waste to consider + * partial paths anywhere in the appendrel unless it's all safe. + * (Child rels visited before this one will be unmarked in + * set_append_rel_pathlist().) + */ + if (!childrel->consider_parallel) + rel->consider_parallel = false; + + /* + * Accumulate size information from each live child. + */ + Assert(childrel->rows > 0); + + /* We have at least one live child. */ + has_live_children = true; + } + + if (!has_live_children) + { + /* + * All children were excluded by constraints, so mark the relation + * ass dummy. We must do this in this phase so that the rel's + * dummy-ness is visible when we generate paths for other rels. + */ + set_dummy_rel_pathlist(rel); + } + else + /* + * Set a non-zero value here to cope with the caller's requirement + * that non-dummy relations are actually not empty. We don't try to + * be accurate here, because we're not going to create a path that + * combines the children outputs. + */ + rel->rows = 1; +} + +/* * set_append_rel_size * Set size estimates for a simple "append relation" * @@ -1045,7 +1390,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, * EquivalenceClass data structures. */ if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) - add_child_rel_equivalences(root, appinfo, rel, childrel); + add_child_rel_equivalences(root, appinfo, rel, childrel, + false); childrel->has_eclass_joins = rel->has_eclass_joins; /* @@ -1327,6 +1673,63 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, } /* + * set_inherit_target_rel_pathlists + * Build access paths for the child target relations + * + * Similar to set_append_rel_pathlist, except that we build paths of the + * children, but don't build an Append path. + */ +static void +set_inherit_target_rel_pathlists(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + ListCell *l; + + /* Nothing to do if all the children were excluded. */ + if (IS_DUMMY_REL(rel)) + return; + + /* Generate access paths for each of the children of passed-in rel */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + /* Re-locate the child RTE and RelOptInfo */ + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + childrel = root->simple_rel_array[childRTindex]; + + /* + * If set_append_rel_size() decided the parent appendrel was + * parallel-unsafe at some point after visiting this child rel, we + * need to propagate the unsafety marking down to the child, so that + * we don't generate useless partial paths for it. + */ + if (!rel->consider_parallel) + childrel->consider_parallel = false; + + /* + * Compute the child's access paths. + */ + set_rel_pathlist(root, childrel, childRTindex, childRTE); + + /* + * If child is dummy, ignore it. + */ + if (IS_DUMMY_REL(childrel)) + continue; + } +} + +/* * set_append_rel_pathlist * Build access paths for an "append relation" */ @@ -2624,6 +3027,139 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows) } /* + * inheritance_make_rel_from_joinlist + * Perform join planning for all non-dummy leaf inheritance children + * in their role as an UPDATE/DELETE query's target relation + * + * If a child relation is a partitioned table, its children are processed in + * turn by recursively calling this function. + */ +static void +inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; +#ifdef USE_ASSERT_CHECKING + Relids all_baserels; +#endif + + /* + * For UPDATE/DELETE queries, the top parent can only ever be a table. + * As a contrast, it could be a UNION ALL subquery in the case of SELECT. + */ + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + Assert(planner_rt_fetch(resultRelation, root)->rtekind == RTE_RELATION); + + /* Nothing to do. */ + if (IS_DUMMY_REL(find_base_rel(root, resultRelation))) + return; + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + PlannerInfo *subroot; + RelOptInfo *childrel; + List *translated_joinlist; + + if (appinfo->parent_relid != resultRelation) + continue; + + subroot = root->inh_target_child_roots[appinfo->child_relid]; + Assert(subroot->parse->resultRelation > 0); + childrel = find_base_rel(root, appinfo->child_relid); + + /* Ignore excluded/pruned children. */ + if (IS_DUMMY_REL(childrel)) + continue; + + root->inh_target_child_rels = lappend_int(root->inh_target_child_rels, + appinfo->child_relid); + + /* + * Modify joinlist such that relations joined to the top parent rel + * appear to be joined to the child rel instead. Do the same for + * any SpecialJoinInfo structs. + */ + translated_joinlist = (List *) + adjust_appendrel_attrs(subroot, + (Node *) joinlist, + 1, &appinfo); + subroot->join_info_list = (List *) + adjust_appendrel_attrs(subroot, + (Node *) root->join_info_list, + 1, &appinfo); + + /* + * Sub-partitioned tables have to be processed recursively using the + * translated subroot as the parent, because AppendRelInfos link + * sub-partitions to their immediate parents, not the root partitioned + * table. + */ + if (childrel->part_scheme != NULL) + { + inheritance_make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * Add this child relation as a placeholder in the parent root's + * inh_target_child_joinrels so that inheritance_planner see same + * number of entries as inh_target_child_rels. + */ + root->inh_target_child_joinrels = + lappend(root->inh_target_child_joinrels, childrel); + + /* Also propagate this child's own children into parent's list. */ + if (subroot->inh_target_child_rels != NIL) + { + root->inh_target_child_rels = + list_concat(root->inh_target_child_rels, + subroot->inh_target_child_rels); + root->inh_target_child_joinrels = + list_concat(root->inh_target_child_joinrels, + subroot->inh_target_child_joinrels); + } + continue; + } + + /* + * Since we added the child rel directly into the join tree, we must + * modify it to be a "base" rel instead of an "other" rel, which the + * join planning code expects the relations being joined to be. + */ + childrel->reloptkind = RELOPT_BASEREL; + + Assert(subroot->join_rel_list == NIL); + Assert(subroot->join_rel_hash == NULL); + + /* Perform join planning and save the resulting RelOptInfo. */ + childrel = make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * Remember this child target rel. inheritance_planner will perform + * the remaining steps of planning for each child relation separately. + * Specifically, it will call grouping_planner on every + * RelOptInfo contained in the inh_target_child_rels list, each of + * which represents the source of tuples to be modified for a given + * target child rel. + */ + root->inh_target_child_joinrels = + lappend(root->inh_target_child_joinrels, childrel); +#ifdef USE_ASSERT_CHECKING + /* + * The following implements essentially the same Assert as in + * make_one_rel, our caller. + */ + all_baserels = bms_copy(root->all_baserels); + all_baserels = bms_del_member(all_baserels, + root->parse->resultRelation); + all_baserels = bms_add_member(all_baserels, + subroot->parse->resultRelation); + Assert(bms_equal(childrel->relids, all_baserels)); +#endif + } +} + +/* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. * diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index b22b36ec0e..029665b974 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -2104,12 +2104,18 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root, * * parent_rel and child_rel could be derived from appinfo, but since the * caller has already computed them, we might as well just pass them in. + * + * If 'child_is_target' is true then the child EC members *replace* the + * corresponding parent members. In that case, 'root' is the child target + * relation's dedicated PlannerInfo so it makes sense to remove the parent + * ECs altogether, because they're of no use. */ void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, - RelOptInfo *child_rel) + RelOptInfo *child_rel, + bool child_is_target) { ListCell *lc1; @@ -2117,6 +2123,8 @@ add_child_rel_equivalences(PlannerInfo *root, { EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc1); ListCell *lc2; + ListCell *prev; + ListCell *next; /* * If this EC contains a volatile expression, then generating child @@ -2134,12 +2142,18 @@ add_child_rel_equivalences(PlannerInfo *root, !bms_is_subset(parent_rel->relids, cur_ec->ec_relids)) continue; - foreach(lc2, cur_ec->ec_members) + prev = NULL; + for (lc2 = list_head(cur_ec->ec_members); lc2 != NULL; lc2 = next) { EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2); + next = lnext(lc2); + if (cur_em->em_is_const) + { + prev = lc2; continue; /* ignore consts here */ + } /* Does it reference parent_rel? */ if (bms_overlap(cur_em->em_relids, parent_rel->relids)) @@ -2177,10 +2191,17 @@ add_child_rel_equivalences(PlannerInfo *root, child_rel->relids); } + /* Delete the parent EC member. */ + if (child_is_target) + cur_ec->ec_members = list_delete_cell(cur_ec->ec_members, + lc2, prev); + (void) add_eq_member(cur_ec, child_expr, new_relids, new_nullable_relids, - true, cur_em->em_datatype); + !child_is_target, cur_em->em_datatype); } + else + prev = lc2; } } } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 91cf78233d..a6c0e5c6a4 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -2022,12 +2022,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) /* * During setrefs.c, we'll need the grouping_map to fix up the cols lists * in GroupingFunc nodes. Save it for setrefs.c to use. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be grouping in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->grouping_map == NULL); root->grouping_map = grouping_map; @@ -2184,12 +2179,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) * with InitPlan output params. (We can't just do that locally in the * MinMaxAgg node, because path nodes above here may have Agg references * as well.) Save the mmaggregates list to tell setrefs.c to do that. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be aggregates in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->minmax_aggs == NIL); root->minmax_aggs = best_path->mmaggregates; diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 9b6cc9e10f..c2d2d12a4c 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -26,7 +26,11 @@ #include "optimizer/paths.h" #include "optimizer/placeholder.h" #include "optimizer/planmain.h" +#include "optimizer/prep.h" +static void add_inherited_target_child_roots(PlannerInfo *root); +static PlannerInfo *adjust_inherited_target_child_root(PlannerInfo *root, + AppendRelInfo *appinfo); /* * query_planner @@ -57,6 +61,8 @@ query_planner(PlannerInfo *root, List *tlist, Query *parse = root->parse; List *joinlist; RelOptInfo *final_rel; + bool inherited_update; + Index rti; /* * If the query has an empty join tree, then it's something easy like @@ -230,14 +236,168 @@ query_planner(PlannerInfo *root, List *tlist, extract_restriction_or_clauses(root); /* + * Construct the all_baserels Relids set. + */ + root->all_baserels = NULL; + for (rti = 1; rti < root->simple_rel_array_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + Assert(brel->relid == rti); /* sanity check on array */ + + /* ignore RTEs that are "other rels" */ + if (brel->reloptkind != RELOPT_BASEREL) + continue; + + root->all_baserels = bms_add_member(root->all_baserels, brel->relid); + } + + inherited_update = (parse->resultRelation && + root->simple_rte_array[parse->resultRelation]->inh); + + /* + * Add child subroots needed to use during planning for individual child + * targets + */ + if (inherited_update) + { + root->inh_target_child_roots = (PlannerInfo **) + palloc0(root->simple_rel_array_size * + sizeof(PlannerInfo *)); + add_inherited_target_child_roots(root); + } + + /* * Ready to do the primary planning. */ final_rel = make_one_rel(root, joinlist); - /* Check that we got at least one usable path */ - if (!final_rel || !final_rel->cheapest_total_path || - final_rel->cheapest_total_path->param_info != NULL) + /* + * Check that we got at least one usable path. In the case of an + * inherited update/delete operation, no path has been created for + * the query's actual target relation yet. + */ + if (!inherited_update && + (!final_rel || + !final_rel->cheapest_total_path || + final_rel->cheapest_total_path->param_info != NULL)) elog(ERROR, "failed to construct the join relation"); return final_rel; } + +/* + * add_inherited_target_child_roots + * Add PlannerInfos for inheritance target children + */ +static void +add_inherited_target_child_roots(PlannerInfo *root) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; + + Assert(root->inh_target_child_roots != NULL); + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + RangeTblEntry *childRTE; + PlannerInfo *subroot; + + if (appinfo->parent_relid != resultRelation) + continue; + + childRTE = root->simple_rte_array[appinfo->child_relid]; + subroot = adjust_inherited_target_child_root(root, appinfo); + + if (childRTE->inh) + add_inherited_target_child_roots(subroot); + + root->inh_target_child_roots[appinfo->child_relid] = subroot; + } +} + +/* + * add_inherit_target_child_root + * This translates query to match the child given by appinfo and + * puts it in a PlannerInfo that will be used for planning the child + * + * The child PlannerInfo reuses most of the parent PlannerInfo's fields + * unchanged, except unexpanded_tlist and processed_tlist are based on the + * child relation. + */ +static PlannerInfo * +adjust_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) +{ + PlannerInfo *subroot; + List *tlist; + ListCell *lc; + + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + + /* Translate the original query's expressions to this child. */ + subroot = makeNode(PlannerInfo); + memcpy(subroot, root, sizeof(PlannerInfo)); + + /* + * Restore the unexpanded tlist for translation, so that child's + * query contains targetList numbered (resnos) per its own + * TupleDesc, which adjust_inherited_tlist ensures. + */ + root->parse->targetList = root->unexpanded_tlist; + subroot->parse = (Query *) adjust_appendrel_attrs(root, + (Node *) root->parse, + 1, &appinfo); + + /* + * Save the original unexpanded targetlist in child subroot, just as + * we did for the parent root, so that this child's own children can use + * it. Must use copy because subroot->parse->targetList will be modified + * soon. + */ + subroot->unexpanded_tlist = list_copy(subroot->parse->targetList); + + /* + * Apply planner's expansion of targetlist, such as adding various junk + * column, filling placeholder entries for dropped columns, etc., all of + * which occurs with the child's TupleDesc. + */ + tlist = preprocess_targetlist(subroot); + subroot->processed_tlist = tlist; + + /* Add any newly added Vars to the child RelOptInfo. */ + build_base_rel_tlists(subroot, tlist); + + /* + * Adjust all_baserels to replace the original target relation with the + * child target relation. Copy it before modifying though. + */ + subroot->all_baserels = bms_copy(root->all_baserels); + subroot->all_baserels = bms_del_member(subroot->all_baserels, + root->parse->resultRelation); + subroot->all_baserels = bms_add_member(subroot->all_baserels, + subroot->parse->resultRelation); + + /* + * Child root should get its own copy of ECs, because they'll be modified + * to replace parent EC expressions by child expressions in + * add_child_rel_equivalences. + */ + subroot->eq_classes = NIL; + foreach(lc, root->eq_classes) + { + EquivalenceClass *ec = lfirst(lc); + EquivalenceClass *new_ec = makeNode(EquivalenceClass); + + memcpy(new_ec, ec, sizeof(EquivalenceClass)); + new_ec->ec_members = list_copy(ec->ec_members); + subroot->eq_classes = lappend(subroot->eq_classes, new_ec); + } + + return subroot; +} diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index b645648559..6cff7262e8 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -37,6 +37,7 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "nodes/relation.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" @@ -126,7 +127,7 @@ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static void inheritance_planner(PlannerInfo *root); static void grouping_planner(PlannerInfo *root, bool inheritance_update, - double tuple_fraction); + RelOptInfo *planned_rel, double tuple_fraction); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, int *tleref_to_colnum_map); @@ -630,7 +631,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->grouping_map = NULL; root->minmax_aggs = NIL; root->qual_security_level = 0; - root->inhTargetKind = INHKIND_NONE; root->hasRecursion = hasRecursion; if (hasRecursion) root->wt_param_id = SS_assign_special_param(root); @@ -970,7 +970,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh) inheritance_planner(root); else - grouping_planner(root, false, tuple_fraction); + grouping_planner(root, false, NULL, tuple_fraction); /* * Capture the set of outer-level param IDs we have access to, for use in @@ -1146,12 +1146,19 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr) * inheritance set. * * We have to handle this case differently from cases where a source relation - * is an inheritance set. Source inheritance is expanded at the bottom of the - * plan tree (see allpaths.c), but target inheritance has to be expanded at - * the top. The reason is that for UPDATE, each target relation needs a - * different targetlist matching its own column set. Fortunately, - * the UPDATE/DELETE target can never be the nullable side of an outer join, - * so it's OK to generate the plan this way. + * is an inheritance set. That's mainly because for UPDATE, each target + * relation needs a different targetlist matching its own column set. So, we + * must modify the source scan/join path for each target relation such that it + * produces the desired target list. + * + * The source scan/join paths for individual target relations are still + * created in allpaths.c by first expanding the inheritance in the usual way + * by set_append_rel_size, followed by join planning for each target relation + * separately in make_one_rel. Finally, we apply grouping_planner here to each + * child scan/join path so that it produces the desired targetlist. + * + * Fortunately, the UPDATE/DELETE target can never be the nullable side of an + * outer join, so it's OK to generate the plan this way. * * Returns nothing; the useful output is in the Paths we attach to * the (UPPERREL_FINAL, NULL) upperrel stored in *root. @@ -1164,14 +1171,8 @@ inheritance_planner(PlannerInfo *root) { Query *parse = root->parse; int top_parentRTindex = parse->resultRelation; - Bitmapset *subqueryRTindexes; - Bitmapset *modifiableARIindexes; int nominalRelation = -1; Index rootRelation = 0; - List *final_rtable = NIL; - int save_rel_array_size = 0; - RelOptInfo **save_rel_array = NULL; - AppendRelInfo **save_append_rel_array = NULL; List *subpaths = NIL; List *subroots = NIL; List *resultRelations = NIL; @@ -1179,70 +1180,59 @@ inheritance_planner(PlannerInfo *root) List *returningLists = NIL; List *rowMarks; RelOptInfo *final_rel; - ListCell *lc; - Index rti; + ListCell *lc1, + *lc2; RangeTblEntry *parent_rte; - PlannerInfo *parent_root; - Query *parent_parse; - Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); - PlannerInfo **parent_roots = NULL; + List *tlist; + standard_qp_extra qp_extra; + RelOptInfo *planned_rel; + /* Inheritance is never used for insert. */ Assert(parse->commandType != CMD_INSERT); + parent_rte = planner_rt_fetch(top_parentRTindex, root); /* - * We generate a modified instance of the original Query for each target - * relation, plan that, and put all the plans into a list that will be - * controlled by a single ModifyTable node. All the instances share the - * same rangetable, but each instance must have its own set of subquery - * RTEs within the finished rangetable because (1) they are likely to get - * scribbled on during planning, and (2) it's not inconceivable that - * subqueries could get planned differently in different cases. We need - * not create duplicate copies of other RTE kinds, in particular not the - * target relations, because they don't have either of those issues. Not - * having to duplicate the target relations is important because doing so - * (1) would result in a rangetable of length O(N^2) for N targets, with - * at least O(N^3) work expended here; and (2) would greatly complicate - * management of the rowMarks list. + * Generate the access paths for all relations mentioned in the query, + * including the target inheritance set. When doing the join planning, + * references in the join tree to the original target relation that's the + * root parent of the inheritance tree is replaced by each of its + * inheritance children and the resulting joinrel RelOptInfo's are + * added to root->inh_target_child_rels. * - * To begin with, generate a bitmapset of the relids of the subquery RTEs. + * Final planning steps (grouping_planner) are applied to the best path + * of each of those child joinrels using a modified instance of the + * original query for a given child target rel. All the paths so + * generated are put into a list that will be controlled by a single + * ModifyTable node. All the instances share the same rangetable. */ - subqueryRTindexes = NULL; - rti = 1; - foreach(lc, parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - if (rte->rtekind == RTE_SUBQUERY) - subqueryRTindexes = bms_add_member(subqueryRTindexes, rti); - rti++; - } /* - * Next, we want to identify which AppendRelInfo items contain references - * to any of the aforesaid subquery RTEs. These items will need to be - * copied and modified to adjust their subquery references; whereas the - * other ones need not be touched. It's worth being tense over this - * because we can usually avoid processing most of the AppendRelInfo - * items, thereby saving O(N^2) space and time when the target is a large - * inheritance tree. We can identify AppendRelInfo items by their - * child_relid, since that should be unique within the list. + * Save the unexpanded version of the query's targetlist to be used below + * for passing to grouping_planner for each child target relation. */ - modifiableARIindexes = NULL; - if (subqueryRTindexes != NULL) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); + root->unexpanded_tlist = list_copy(root->parse->targetList); - if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) || - bms_is_member(appinfo->child_relid, subqueryRTindexes) || - bms_overlap(pull_varnos((Node *) appinfo->translated_vars), - subqueryRTindexes)) - modifiableARIindexes = bms_add_member(modifiableARIindexes, - appinfo->child_relid); - } + /* Do the scan/join planning. */ + tlist = preprocess_targetlist(root); + root->processed_tlist = tlist; + qp_extra.tlist = tlist; + qp_extra.activeWindows = qp_extra.groupClause = NIL; + planned_rel = query_planner(root, tlist, standard_qp_callback, &qp_extra); + + /* + * If it turned out during query planning that all the children are dummy + * (pruned or excluded by constraints), no need to do the steps below. + * Let grouping_planner finish up the final path. + */ + if (IS_DUMMY_REL(planned_rel)) + { + grouping_planner(root, false, planned_rel, 0.0); + return; } + Assert(planned_rel->relid == top_parentRTindex); + Assert(planned_rel->reloptkind == RELOPT_BASEREL); + /* * If the parent RTE is a partitioned table, we should use that as the * nominal target relation, because the RTEs added for partitioned tables @@ -1250,7 +1240,6 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1258,95 +1247,42 @@ inheritance_planner(PlannerInfo *root) } /* - * The PlannerInfo for each child is obtained by translating the relevant - * members of the PlannerInfo for its immediate parent, which we find - * using the parent_relid in its AppendRelInfo. We save the PlannerInfo - * for each parent in an array indexed by relid for fast retrieval. Since - * the maximum number of parents is limited by the number of RTEs in the - * query, we use that number to allocate the array. An extra entry is - * needed since relids start from 1. + * Get on with finalizing the path for each child target relation by + * calling grouping_planner on its joinrel. Note that we're restoring + * the query's targetlist to the original one for grouping_planner's + * targetlist expansion steps to perform the expansion with individual + * child descriptors. */ - parent_roots = (PlannerInfo **) palloc0((list_length(parse->rtable) + 1) * - sizeof(PlannerInfo *)); - parent_roots[top_parentRTindex] = root; - - /* - * And now we can get on with generating a plan for each child table. - */ - foreach(lc, root->append_rel_list) + forboth(lc1, root->inh_target_child_rels, + lc2, root->inh_target_child_joinrels) { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - PlannerInfo *subroot; + Index childRTindex = lfirst_int(lc1); + RelOptInfo *childrel = lfirst(lc2); + PlannerInfo *subroot = root->inh_target_child_roots[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; RangeTblEntry *child_rte; RelOptInfo *sub_final_rel; Path *subpath; - /* append_rel_list contains all append rels; ignore others */ - if (!bms_is_member(appinfo->parent_relid, parent_relids)) - continue; - - /* - * expand_inherited_rtentry() always processes a parent before any of - * that parent's children, so the parent_root for this relation should - * already be available. - */ - parent_root = parent_roots[appinfo->parent_relid]; - Assert(parent_root != NULL); - parent_parse = parent_root->parse; - - /* - * We need a working copy of the PlannerInfo so that we can control - * propagation of information back to the main copy. - */ - subroot = makeNode(PlannerInfo); - memcpy(subroot, parent_root, sizeof(PlannerInfo)); - - /* - * Generate modified query with this rel as target. We first apply - * adjust_appendrel_attrs, which copies the Query and changes - * references to the parent RTE to refer to the current child RTE, - * then fool around with subquery RTEs. - */ - subroot->parse = (Query *) - adjust_appendrel_attrs(parent_root, - (Node *) parent_parse, - 1, &appinfo); + Assert(subroot != NULL); + Assert(subroot->parse->resultRelation == childRTindex); /* * If there are securityQuals attached to the parent, move them to the * child rel (they've already been transformed properly for that). */ - parent_rte = rt_fetch(appinfo->parent_relid, subroot->parse->rtable); - child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable); + parent_rte = planner_rt_fetch(appinfo->parent_relid, subroot); + child_rte = planner_rt_fetch(appinfo->child_relid, subroot); child_rte->securityQuals = parent_rte->securityQuals; parent_rte->securityQuals = NIL; /* - * Mark whether we're planning a query to a partitioned table or an - * inheritance parent. + * Ignore a partitioned child. Instead, the paths of its children will + * be added to subpaths. */ - subroot->inhTargetKind = - (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED; - - /* - * If this child is further partitioned, remember it as a parent. - * Since a partitioned table does not have any data, we don't need to - * create a plan for it, and we can stop processing it here. We do, - * however, need to remember its modified PlannerInfo for use when - * processing its children, since we'll update their varnos based on - * the delta from immediate parent to child, not from top to child. - * - * Note: a very non-obvious point is that we have not yet added - * duplicate subquery RTEs to the subroot's rtable. We mustn't, - * because then its children would have two sets of duplicates, - * confusing matters. - */ - if (child_rte->inh) + if (childrel->part_scheme) { - Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE); - parent_relids = bms_add_member(parent_relids, appinfo->child_relid); - parent_roots[appinfo->child_relid] = subroot; - + Assert(child_rte->inh); continue; } @@ -1374,111 +1310,8 @@ inheritance_planner(PlannerInfo *root) if (nominalRelation < 0) nominalRelation = appinfo->child_relid; - /* - * The rowMarks list might contain references to subquery RTEs, so - * make a copy that we can apply ChangeVarNodes to. (Fortunately, the - * executor doesn't need to see the modified copies --- we can just - * pass it the original rowMarks list.) - */ - subroot->rowMarks = copyObject(parent_root->rowMarks); - - /* - * The append_rel_list likewise might contain references to subquery - * RTEs (if any subqueries were flattenable UNION ALLs). So prepare - * to apply ChangeVarNodes to that, too. As explained above, we only - * want to copy items that actually contain such references; the rest - * can just get linked into the subroot's append_rel_list. - * - * If we know there are no such references, we can just use the outer - * append_rel_list unmodified. - */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - subroot->append_rel_list = NIL; - foreach(lc2, parent_root->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, modifiableARIindexes)) - appinfo2 = copyObject(appinfo2); - - subroot->append_rel_list = lappend(subroot->append_rel_list, - appinfo2); - } - } - - /* - * Add placeholders to the child Query's rangetable list to fill the - * RT indexes already reserved for subqueries in previous children. - * These won't be referenced, so there's no need to make them very - * valid-looking. - */ - while (list_length(subroot->parse->rtable) < list_length(final_rtable)) - subroot->parse->rtable = lappend(subroot->parse->rtable, - makeNode(RangeTblEntry)); - - /* - * If this isn't the first child Query, generate duplicates of all - * subquery RTEs, and adjust Var numbering to reference the - * duplicates. To simplify the loop logic, we scan the original rtable - * not the copy just made by adjust_appendrel_attrs; that should be OK - * since subquery RTEs couldn't contain any references to the target - * rel. - */ - if (final_rtable != NIL && subqueryRTindexes != NULL) - { - ListCell *lr; - - rti = 1; - foreach(lr, parent_parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lr); - - if (bms_is_member(rti, subqueryRTindexes)) - { - Index newrti; - - /* - * The RTE can't contain any references to its own RT - * index, except in its securityQuals, so we can save a - * few cycles by applying ChangeVarNodes to the rest of - * the rangetable before we append the RTE to it. - */ - newrti = list_length(subroot->parse->rtable) + 1; - ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); - ChangeVarNodes((Node *) subroot->rowMarks, rti, newrti, 0); - /* Skip processing unchanging parts of append_rel_list */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - foreach(lc2, subroot->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, - modifiableARIindexes)) - ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); - } - } - rte = copyObject(rte); - ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); - subroot->parse->rtable = lappend(subroot->parse->rtable, - rte); - } - rti++; - } - } - - /* There shouldn't be any OJ info to translate, as yet */ - Assert(subroot->join_info_list == NIL); - /* and we haven't created PlaceHolderInfos, either */ - Assert(subroot->placeholder_list == NIL); - - /* Generate Path(s) for accessing this result relation */ - grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ ); + /* Apply the top-level targetlist to childrel's path. */ + grouping_planner(subroot, true, childrel, 0.0); /* * Select cheapest path in case there's more than one. We always run @@ -1490,45 +1323,10 @@ inheritance_planner(PlannerInfo *root) subpath = sub_final_rel->cheapest_total_path; /* - * If this child rel was excluded by constraint exclusion, exclude it - * from the result plan. + * child rel cannot be empty, or inheritance_make_rel_from_joinlist + * wouldn't have put it in the list to begin with. */ - if (IS_DUMMY_PATH(subpath)) - continue; - - /* - * If this is the first non-excluded child, its post-planning rtable - * becomes the initial contents of final_rtable; otherwise, append - * just its modified subquery RTEs to final_rtable. - */ - if (final_rtable == NIL) - final_rtable = subroot->parse->rtable; - else - final_rtable = list_concat(final_rtable, - list_copy_tail(subroot->parse->rtable, - list_length(final_rtable))); - - /* - * We need to collect all the RelOptInfos from all child plans into - * the main PlannerInfo, since setrefs.c will need them. We use the - * last child's simple_rel_array (previous ones are too short), so we - * have to propagate forward the RelOptInfos that were already built - * in previous children. - */ - Assert(subroot->simple_rel_array_size >= save_rel_array_size); - for (rti = 1; rti < save_rel_array_size; rti++) - { - RelOptInfo *brel = save_rel_array[rti]; - - if (brel) - subroot->simple_rel_array[rti] = brel; - } - save_rel_array_size = subroot->simple_rel_array_size; - save_rel_array = subroot->simple_rel_array; - save_append_rel_array = subroot->append_rel_array; - - /* Make sure any initplans from this rel get into the outer list */ - root->init_plans = subroot->init_plans; + Assert(!IS_DUMMY_PATH(subpath)); /* Build list of sub-paths */ subpaths = lappend(subpaths, subpath); @@ -1560,36 +1358,6 @@ inheritance_planner(PlannerInfo *root) */ /* - * If we managed to exclude every child rel, return a dummy plan; it - * doesn't even need a ModifyTable node. - */ - if (subpaths == NIL) - { - set_dummy_rel_pathlist(final_rel); - return; - } - - /* - * Put back the final adjusted rtable into the master copy of the Query. - * (We mustn't do this if we found no non-excluded children.) - */ - parse->rtable = final_rtable; - root->simple_rel_array_size = save_rel_array_size; - root->simple_rel_array = save_rel_array; - root->append_rel_array = save_append_rel_array; - - /* Must reconstruct master's simple_rte_array, too */ - root->simple_rte_array = (RangeTblEntry **) - palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *)); - rti = 1; - foreach(lc, final_rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - root->simple_rte_array[rti++] = rte; - } - - /* * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will * have dealt with fetching non-locked marked rows, else we need to have * ModifyTable do that. @@ -1629,6 +1397,12 @@ inheritance_planner(PlannerInfo *root) * (inheritance_planner will create a single ModifyTable node covering all the * target tables.) * + * If non-NULL, planned_rel is a RelOptInfo containing paths for the query's + * top-level joinrel, which the caller produced by itself. In that case, this + * function only needs to adjust the targetlist of its cheapest_total_path. + * The only caller that may pass such a RelOptInfo currently is + * inheritance_planner. + * * tuple_fraction is the fraction of tuples we expect will be retrieved. * tuple_fraction is interpreted as follows: * 0: expect all tuples to be retrieved (normal case) @@ -1647,6 +1421,7 @@ inheritance_planner(PlannerInfo *root) */ static void grouping_planner(PlannerInfo *root, bool inheritance_update, + RelOptInfo *planned_rel, double tuple_fraction) { Query *parse = root->parse; @@ -1659,7 +1434,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, List *final_targets; List *final_targets_contain_srfs; bool final_target_parallel_safe; - RelOptInfo *current_rel; + RelOptInfo *current_rel = planned_rel; RelOptInfo *final_rel; ListCell *lc; @@ -1699,6 +1474,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * special work for recursive unions is the responsibility of * plan_set_operations. */ + Assert(current_rel == NULL); current_rel = plan_set_operations(root); /* @@ -1788,17 +1564,26 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, parse->groupClause = preprocess_groupclause(root, NIL); } - /* Preprocess targetlist */ - tlist = preprocess_targetlist(root); - /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation so - * that we can transfer its decoration (resnames etc) to the topmost - * tlist of the finished Plan. + * Preprocess targetlist, if needed. If the caller has already done + * query planning, root->processed_tlist already contains the desired + * targetlist. */ - root->processed_tlist = tlist; + if (planned_rel == NULL) + { + tlist = preprocess_targetlist(root); + + /* + * We are now done hacking up the query's targetlist. Most of the + * remaining planning work will be done with the PathTarget + * representation of tlists, but save aside the full representation + * so that we can transfer its decoration (resnames etc) to the + * topmost tlist of the finished Plan. + */ + root->processed_tlist = tlist; + } + else + tlist = root->processed_tlist; /* * Collect statistics about aggregates for estimating costs, and mark @@ -1878,8 +1663,9 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * We also generate (in standard_qp_callback) pathkey representations * of the query's sort clause, distinct clause, etc. */ - current_rel = query_planner(root, tlist, - standard_qp_callback, &qp_extra); + if (current_rel == NULL) + current_rel = query_planner(root, tlist, + standard_qp_callback, &qp_extra); /* * Convert the query's result tlist into PathTarget format. diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index cd6e11904e..78baec00dc 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -917,7 +917,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->grouping_map = NULL; subroot->minmax_aggs = NIL; subroot->qual_security_level = 0; - subroot->inhTargetKind = INHKIND_NONE; subroot->hasRecursion = false; subroot->wt_param_id = -1; subroot->non_recursive_path = NULL; diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index da278f785e..b320f8dce4 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -2250,8 +2250,39 @@ adjust_appendrel_attrs_mutator(Node *node, context->appinfos); return (Node *) phv; } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index a570ac0aab..a0fed5be42 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1264,36 +1264,6 @@ get_relation_constraints(PlannerInfo *root, } } - /* - * Append partition predicates, if any. - * - * For selects, partition pruning uses the parent table's partition bound - * descriptor, instead of constraint exclusion which is driven by the - * individual partition's partition constraint. - */ - if (enable_partition_pruning && root->parse->commandType != CMD_SELECT) - { - List *pcqual = RelationGetPartitionQual(relation); - - if (pcqual) - { - /* - * Run the partition quals through const-simplification similar to - * check constraints. We skip canonicalize_qual, though, because - * partition quals should be in canonical form already; also, - * since the qual is in implicit-AND format, we'd have to - * explicitly convert it to explicit-AND format and back again. - */ - pcqual = (List *) eval_const_expressions(root, (Node *) pcqual); - - /* Fix Vars to have the desired varno */ - if (varno != 1) - ChangeVarNodes((Node *) pcqual, 1, varno, 0); - - result = list_concat(result, pcqual); - } - } - heap_close(relation, NoLock); return result; @@ -1420,31 +1390,15 @@ relation_excluded_by_constraints(PlannerInfo *root, switch (constraint_exclusion) { case CONSTRAINT_EXCLUSION_OFF: - - /* - * Don't prune if feature turned off -- except if the relation is - * a partition. While partprune.c-style partition pruning is not - * yet in use for all cases (update/delete is not handled), it - * would be a UI horror to use different user-visible controls - * depending on such a volatile implementation detail. Therefore, - * for partitioned tables we use enable_partition_pruning to - * control this behavior. - */ - if (root->inhTargetKind == INHKIND_PARTITIONED) - break; return false; case CONSTRAINT_EXCLUSION_PARTITION: /* * When constraint_exclusion is set to 'partition' we only handle - * OTHER_MEMBER_RELs, or BASERELs in cases where the result target - * is an inheritance parent or a partitioned table. + * OTHER_MEMBER_RELs. */ - if ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) && - !(rel->reloptkind == RELOPT_BASEREL && - root->inhTargetKind != INHKIND_NONE && - rel->relid == root->parse->resultRelation)) + if (rel->reloptkind != RELOPT_OTHER_MEMBER_REL) return false; break; diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 6fd24203dd..53e9812237 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -319,9 +319,6 @@ typedef struct PlannerInfo Index qual_security_level; /* minimum security_level for quals */ /* Note: qual_security_level is zero if there are no securityQuals */ - InheritanceKind inhTargetKind; /* indicates if the target relation is an - * inheritance child or partition or a - * partitioned table */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */ bool hasDeletedRTEs; /* true if any RTE was deleted from jointree */ @@ -343,6 +340,37 @@ typedef struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* + * The following fields are set during query planning portion of an + * inherited UPDATE/DEELETE operation. + */ + + /* + * This stores the original version of the query's targetlist that's + * not modified by the planner. + */ + List *unexpanded_tlist; + + /* + * PlannerInfos corresponding to each inheritance child targets. + * Content of each PlannerInfo is same as the parent PlannerInfo, except + * for the parse tree which is a translated copy of the parent's parse + * tree. + */ + struct PlannerInfo **inh_target_child_roots; + + /* List of RT indexes of child RT indexes. */ + List *inh_target_child_rels; + + /* + * RelOptInfos corresponding to each child target rel. For leaf children, + * it's the RelOptInfo representing the output of make_rel_from_joinlist() + * called with the parent rel in the original join tree replaced by a + * given leaf child. For non-leaf children, it's the baserel RelOptInfo + * itself, left as a placeholder. + */ + List *inh_target_child_joinrels; } PlannerInfo; diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index cafde307ad..7aa1b6e17f 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -161,7 +161,8 @@ extern EquivalenceClass *match_eclasses_to_foreign_key_col(PlannerInfo *root, extern void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, - RelOptInfo *child_rel); + RelOptInfo *child_rel, + bool child_is_target); extern List *generate_implied_equalities_for_column(PlannerInfo *root, RelOptInfo *rel, ec_matches_callback_type callback, diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index c55de5d476..c9f02c1fb0 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1780,7 +1780,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_1 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_1 -> Nested Loop Semi Join @@ -1788,7 +1788,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_2 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_2 (28 rows) -- 2.11.0
v10-0002-Store-inheritance-root-parent-index-in-otherrel-.patch (text/plain, 2.5K) download From 38c2ae592397a78e234d7c6e1d866b9dfd51cd5f Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 26 Oct 2018 16:45:59 +0900 Subject: [PATCH v10 2/6] Store inheritance root parent index in otherrel's RelOptInfo Although it's set by build_simple_rel, it's not being used by any code yet. --- src/backend/nodes/outfuncs.c | 1 + src/backend/optimizer/util/relnode.c | 14 ++++++++++++++ src/include/nodes/relation.h | 4 ++++ 3 files changed, 19 insertions(+) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 3c9db22507..c5fd84f4aa 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2271,6 +2271,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_BOOL_FIELD(consider_partitionwise_join); WRITE_BITMAPSET_FIELD(top_parent_relids); WRITE_NODE_FIELD(partitioned_child_rels); + WRITE_UINT_FIELD(inh_root_parent); } static void diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 39f5729b91..29ba19349f 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -215,9 +215,23 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->top_parent_relids = parent->top_parent_relids; else rel->top_parent_relids = bms_copy(parent->relids); + + /* + * For inheritance child relations, we also set inh_root_parent. + * Note that 'parent' might itself be a child (a sub-partitioned + * partition), in which case we simply use its value of + * inh_root_parent. + */ + if (parent->rtekind == RTE_RELATION) + rel->inh_root_parent = parent->inh_root_parent > 0 ? + parent->inh_root_parent : + parent->relid; } else + { rel->top_parent_relids = NULL; + rel->inh_root_parent = 0; + } /* Check type of rtable entry */ switch (rte->rtekind) diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 53e9812237..7f71f1c948 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -731,6 +731,10 @@ typedef struct RelOptInfo List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ + + Index inh_root_parent; /* For otherrels, this is the RT index of + * inheritance table mentioned in the query + * from which this relation originated */ } RelOptInfo; /* -- 2.11.0
v10-0003-Lazy-creation-of-RTEs-for-inheritance-children.patch (text/plain, 91.5K) download From b668d6e3763d713b0179054d0e448a0b8f6dbcd7 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 18:14:13 +0900 Subject: [PATCH v10 3/6] Lazy creation of RTEs for inheritance children Currently, expand_inherited_tables called from subquery_planner opens all inheritance child tables and adds them to the query's range table and PlannerInfo in the form of AppendRelInfo's initially and later RelOptInfo's. For partitioning, that's pretty wasteful, because it's possible to determine only the partitions that will need to be scanned using partition pruning, which doesn't require opening the partitions themselves. However, the point at which the above processing happens is too early to perform partition pruning. This commit rearranges things within the planner so that inheritance child relations can be added to the Query and PlannerInfo during make_one_rel, just before set_base_rel_sizes is called. Although the late initialization approach only benefits the partitioning case, it seems undesirable to do it only for partitioned tables, because it means adding code to handle partitions specially at various places within the planner. So, *all* inheritance parent tables are expanded in make_one_rel. All unpruned partitions are added to Query in the form of their RangeTblEntry's being added to the range table and to PlannerInfo in the form of an AppendRelInfo and a RelOptInfo for each. For partitioning, although we don't create a RangeTblEntry and RelOptInfo for pruned partitions at make_one_rel time, partitionwise join code relies on the fact that even though partitions may have been pruned, they'd still own a RelOptInfo to handle the outer join case where the pruned partition appears on the nullable side of join. Partitionwise join code deals with that by allocating dummy RelOptInfos for pruned partitions that are based mostly on their parent's properties. There are some regression test diffs: 1. Caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child, as seen in the partition_aggregate.out test output. 2. Those in postgres_fdw.out are caused by the fact that junk columns required for row marking are added to reltarget->exprs later than user columns, because the row marking junk columns arent added until after the inheritance is expanded which as of this commit is later than it used to be. --- contrib/postgres_fdw/expected/postgres_fdw.out | 32 +- src/backend/optimizer/path/allpaths.c | 355 +-------- src/backend/optimizer/path/joinrels.c | 62 ++ src/backend/optimizer/plan/initsplan.c | 58 -- src/backend/optimizer/plan/planmain.c | 12 +- src/backend/optimizer/plan/planner.c | 65 +- src/backend/optimizer/plan/setrefs.c | 6 + src/backend/optimizer/prep/preptlist.c | 15 +- src/backend/optimizer/prep/prepunion.c | 854 +++++++++++++++------- src/backend/optimizer/util/Makefile | 2 +- src/backend/optimizer/util/plancat.c | 40 +- src/backend/optimizer/util/relnode.c | 80 +- src/backend/partitioning/partprune.c | 43 +- src/include/nodes/relation.h | 8 + src/include/optimizer/pathnode.h | 1 + src/include/optimizer/plancat.h | 2 +- src/include/optimizer/prep.h | 9 +- src/include/partitioning/partprune.h | 2 +- src/test/regress/expected/partition_aggregate.out | 4 +- 19 files changed, 886 insertions(+), 764 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index bb92d9d37a..33a33606ef 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7072,15 +7072,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7110,15 +7110,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7147,15 +7147,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid @@ -7165,15 +7165,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (39 rows) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 3f3bfbd696..e8f9463afb 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -44,7 +44,6 @@ #include "optimizer/var.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -378,7 +377,11 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, */ set_dummy_rel_pathlist(rel); } - else if (rte->inh) + /* + * expand_inherited_tables may have proved that the relation is empty, so + * check if it's so. + */ + else if (rte->inh && !IS_DUMMY_REL(rel)) { /* * If it's a target relation, set the sizes of children instead. @@ -949,38 +952,18 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, int parentRTindex = rti; ListCell *l; bool has_live_children; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); Assert(IS_SIMPLE_REL(rel)); - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - foreach(l, root->append_rel_list) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; PlannerInfo *subroot; ListCell *lc; List *translated_exprs, @@ -995,139 +978,18 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, subroot = root->inh_target_child_roots[childRTindex]; /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo is created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals. */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); + if (IS_DUMMY_REL(childrel)) continue; - } - - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } if (relation_excluded_by_constraints(root, childrel, childRTE)) { @@ -1269,8 +1131,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -1278,32 +1138,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Assert(IS_SIMPLE_REL(rel)); /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, we will bubble up - * the indexes of partitioned relations that appear down in the tree, so - * that when we've created Paths for all the children, the root - * partitioned table's list will contain all such indexes. - */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - rel->partitioned_child_rels = list_make1_int(rti); - - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - - /* * If this is a partitioned baserel, set the consider_partitionwise_join * flag; currently, we only consider partitionwise joins with the baserel * if its targetlist doesn't contain a whole-row Var. @@ -1340,12 +1174,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; ListCell *parentvars; ListCell *childvars; - ListCell *lc; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1353,18 +1183,34 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo is created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); /* - * Copy/Modify targetlist. Even if this child is deemed empty, we need - * its targetlist in case it falls on nullable side in a child-join - * because of partitionwise join. + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals. + */ + if (IS_DUMMY_REL(childrel)) + continue; + + if (relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* + * This child need not be scanned, so we can omit it from the + * appendrel. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Copy/Modify targetlist. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -1394,144 +1240,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, false); childrel->has_eclass_joins = rel->has_eclass_joins; - /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* - * This child need not be scanned, so we can omit it from the - * appendrel. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - /* CE failed, so finish copying/modifying join quals. */ childrel->joininfo = (List *) adjust_appendrel_attrs(root, @@ -3068,6 +2776,7 @@ inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) subroot = root->inh_target_child_roots[appinfo->child_relid]; Assert(subroot->parse->resultRelation > 0); childrel = find_base_rel(root, appinfo->child_relid); + Assert(childrel != NULL); /* Ignore excluded/pruned children. */ if (IS_DUMMY_REL(childrel)) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index d3d21fed5d..da0831de4e 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -20,6 +20,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/prep.h" +#include "optimizer/tlist.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -46,6 +47,9 @@ static void try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, List *parent_restrictlist); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + int partidx); /* @@ -1376,6 +1380,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts); + if (child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts); + /* We should never try to join two overlapping sets of rels. */ Assert(!bms_overlap(child_rel1->relids, child_rel2->relids)); child_joinrelids = bms_union(child_rel1->relids, child_rel2->relids); @@ -1582,3 +1591,56 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx) +{ + RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + /* Pass parent's info as for both the parent rel and child rel. */ + AppendRelInfo *appinfo = make_append_rel_info(parent, parentrte, + parent->tupdesc, + parentrte->relid, + parent->reltype, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 01335db511..7636aa82c4 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -616,64 +616,6 @@ create_lateral_join_info(PlannerInfo *root) bms_add_member(brel2->lateral_referencers, rti); } } - - /* - * Lastly, propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - */ - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - RangeTblEntry *brte = root->simple_rte_array[rti]; - - /* - * Skip empty slots. Also skip non-simple relations i.e. dead - * relations. - */ - if (brel == NULL || !IS_SIMPLE_REL(brel)) - continue; - - /* - * In the case of table inheritance, the parent RTE is directly linked - * to every child table via an AppendRelInfo. In the case of table - * partitioning, the inheritance hierarchy is expanded one level at a - * time rather than flattened. Therefore, an other member rel that is - * a partitioned table may have children of its own, and must - * therefore be marked with the appropriate lateral info so that those - * children eventually get marked also. - */ - Assert(brte); - if (brel->reloptkind == RELOPT_OTHER_MEMBER_REL && - (brte->rtekind != RTE_RELATION || - brte->relkind != RELKIND_PARTITIONED_TABLE)) - continue; - - if (brte->inh) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); - RelOptInfo *childrel; - - if (appinfo->parent_relid != rti) - continue; - childrel = root->simple_rel_array[appinfo->child_relid]; - Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - Assert(childrel->direct_lateral_relids == NULL); - childrel->direct_lateral_relids = brel->direct_lateral_relids; - Assert(childrel->lateral_relids == NULL); - childrel->lateral_relids = brel->lateral_relids; - Assert(childrel->lateral_referencers == NULL); - childrel->lateral_referencers = brel->lateral_referencers; - } - } - } } diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index c2d2d12a4c..be261061f2 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -256,6 +256,16 @@ query_planner(PlannerInfo *root, List *tlist, root->all_baserels = bms_add_member(root->all_baserels, brel->relid); } + /* + * Expand RT entries that represent inherited or partitioned tables. + * This will perform partition pruning on partitioned tables in the + * original range table and also recursively on any child partitioned + * tables that were added by the expansion of the original parent(s). + * As new entries are added to the range table, various arrays in the + * PlannerInfo will be expanded accordingly. + */ + expand_inherited_tables(root); + inherited_update = (parse->resultRelation && root->simple_rte_array[parse->resultRelation]->inh); @@ -367,7 +377,7 @@ adjust_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) * column, filling placeholder entries for dropped columns, etc., all of * which occurs with the child's TupleDesc. */ - tlist = preprocess_targetlist(subroot); + tlist = preprocess_targetlist(subroot, true); subroot->processed_tlist = tlist; /* Add any newly added Vars to the child RelOptInfo. */ diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 6cff7262e8..148032342f 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -23,6 +23,7 @@ #include "access/sysattr.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -699,27 +700,24 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } /* * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). + * pullup (so that all non-inherited RTEs are present). */ preprocess_rowmarks(root); /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain base relations not joins, so it's OK (and marginally more - * efficient) to do it after checking for join RTEs. We must do it after - * pulling up subqueries, else we'd fail to handle inherited tables in - * subqueries. - */ - expand_inherited_tables(root); - - /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -1212,8 +1210,11 @@ inheritance_planner(PlannerInfo *root) */ root->unexpanded_tlist = list_copy(root->parse->targetList); - /* Do the scan/join planning. */ - tlist = preprocess_targetlist(root); + /* + * Do the scan/join planning. We haven't expanded inheritance yet, so + * pass false. + */ + tlist = preprocess_targetlist(root, false); root->processed_tlist = tlist; qp_extra.tlist = tlist; qp_extra.activeWindows = qp_extra.groupClause = NIL; @@ -1222,9 +1223,10 @@ inheritance_planner(PlannerInfo *root) /* * If it turned out during query planning that all the children are dummy * (pruned or excluded by constraints), no need to do the steps below. - * Let grouping_planner finish up the final path. + * For regular inheritance case, it's possible that we only need to modify + * the parent table. Let grouping_planner finish up the final path. */ - if (IS_DUMMY_REL(planned_rel)) + if (!parent_rte->inh || IS_DUMMY_REL(planned_rel)) { grouping_planner(root, false, planned_rel, 0.0); return; @@ -1571,14 +1573,19 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, */ if (planned_rel == NULL) { - tlist = preprocess_targetlist(root); + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root, false); /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation - * so that we can transfer its decoration (resnames etc) to the - * topmost tlist of the finished Plan. + * We are now mostly done hacking up the query's targetlist. More + * columns might be added during query_planner after inheritance + * expansion in make_one_rel, because some children may require + * different column than the parent for row mark handling; see + * add_rowmark_junk_columns(). Most of the remaining planning + * work will be done with the PathTarget representation of tlists, + * but save aside the full representation so that we can transfer + * its decoration (resnames etc) to the topmost tlist of the + * finished Plan. */ root->processed_tlist = tlist; } @@ -2354,7 +2361,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2379,7 +2386,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } @@ -6816,6 +6823,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -6917,6 +6928,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 920a99d0d9..03c7b23974 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -325,6 +325,12 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) UPPERREL_FINAL, NULL))) add_rtes_to_flat_rtable(rel->subroot, true); } + /* + * A NULL rel also means an unplanned subquery rte, so apply + * flatten_unplanned_rtes. + */ + else + flatten_unplanned_rtes(glob, rte); } rti++; } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 8603feef2b..0747403acd 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -65,9 +65,13 @@ static List *expand_targetlist(List *tlist, int command_type, * * As a side effect, if there's an ON CONFLICT UPDATE clause, its targetlist * is also preprocessed (and updated in-place). + * + * inheritance_expanded specifies whether inheritance tables are expanded + * as of calling this function. Caller must specify it because that affects + * which row marking related junk columns get added to the targetlist. */ List * -preprocess_targetlist(PlannerInfo *root) +preprocess_targetlist(PlannerInfo *root, bool inheritance_expanded) { Query *parse = root->parse; int result_relation = parse->resultRelation; @@ -134,6 +138,15 @@ preprocess_targetlist(PlannerInfo *root) if (rc->rti != rc->prti) continue; + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which will change the parent row + * mark's allMarkTypes fields. + */ + if (rc->isParent && !inheritance_expanded) + continue; + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) { /* Need to fetch TID */ diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index b320f8dce4..303554de34 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -39,15 +39,19 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" +#include "optimizer/var.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" +#include "partitioning/partprune.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/selfuncs.h" @@ -99,23 +103,26 @@ static List *generate_append_tlist(List *colTypes, List *colCollations, List *input_tlists, List *refnames_tlist); static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist); +static void expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti); static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); + Index rti, RelOptInfo *rel); static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); -static void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p); +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); +static RelOptInfo *build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, @@ -1472,33 +1479,134 @@ generate_setop_grouplist(SetOperationStmt *op, List *targetlist) void expand_inherited_tables(PlannerInfo *root) { - Index nrtes; + int orig_rtable_size; Index rti; - ListCell *rl; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; /* - * expand_inherited_rtentry may add RTEs to parse->rtable. The function is + * expand_append_rtentry may add RTEs to parse->rtable. The function is * expected to recursively handle any RTEs that it creates with inh=true. * So just scan as far as the original end of the rtable list. */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) + for (rti = 1; rti < orig_rtable_size; rti++) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + if (rte->inh) + expand_append_rtentry(root, brel, rte, rti); + } +} + +/* + * expand_append_rtentry + * This initializes RelOptInfos for an appendrel's child relations, if + * any + * + * 'rel' is the appendrel parent, whose range table entry ('rte') has been + * marked to require adding children. An appendrel parent could either + * be a subquery (if we flattened UNION ALL query) or a table that's known + * to have inheritance children. The latter consists of both regular + * inheritance parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as an appendrel child. The original RTE is + * considered to represent the whole inheritance set. + */ +static void +expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, + Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_append_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_inherited_rtentry(root, rte, rti, rel); } } /* * expand_inherited_rtentry - * Check whether a rangetable entry represents an inheritance set. - * If so, add entries for all the child tables to the query's - * rangetable, and build AppendRelInfo nodes for all the child tables - * and add them to root->append_rel_list. If not, clear the entry's - * "inh" flag to prevent later code from looking for AppendRelInfos. + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. * * Note that the original RTE is considered to represent the whole * inheritance set. The first of the generated RTEs is an RTE for the same @@ -1509,55 +1617,33 @@ expand_inherited_tables(PlannerInfo *root) * regular inheritance, a parent RTE must always have at least two associated * AppendRelInfos: one corresponding to the parent table as a simple member of * inheritance set and one or more corresponding to the actual children. - * Since a partitioned table is not scanned, it might have only one associated - * AppendRelInfo. */ static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, + RelOptInfo *rel) { Oid parentOID; PlanRowMark *oldrc; - Relation oldrelation; - LOCKMODE lockmode; + LOCKMODE lockmode = rte->rellockmode; List *inhOIDs; ListCell *l; + int num_children; + int num_children_added = 0; - /* Does RT entry allow inheritance? */ - if (!rte->inh) - return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } - /* Fast path for common case of childless table */ + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); parentOID = rte->relid; - if (!has_subclass(parentOID)) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * The rewriter should already have obtained an appropriate lock on each - * relation named in the query. However, for each child relation we add - * to the query, we must obtain an appropriate lock, because this will be - * the first use of those relations in the parse/rewrite/plan pipeline. - * Child rels should use the same lockmode as their parent. - */ - lockmode = rte->rellockmode; /* Scan for all members of inheritance set, acquire needed locks */ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); /* * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite above has_subclass() check, if table - * once had a child but no longer does. + * case. This could happen despite has_subclass() check performed by + * subquery_planner, if table once had a child but no longer does. */ - if (list_length(inhOIDs) < 2) + num_children = list_length(inhOIDs); + if (num_children < 2) { /* Clear flag before returning */ rte->inh = false; @@ -1565,216 +1651,219 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } /* - * If parent relation is selected FOR UPDATE/SHARE, we need to mark its - * PlanRowMark as isParent = true, and generate a new PlanRowMark for each - * child. + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. */ oldrc = get_plan_rowmark(root->rowMarks, rti); - if (oldrc) - oldrc->isParent = true; + Assert(oldrc == NULL || oldrc->isParent); /* - * Must open the parent relation to examine its tupdesc. We need not lock - * it; we assume the rewriter already did. + * Must expand PlannerInfo arrays by num_children before we can add + * children. */ - oldrelation = heap_open(parentOID, NoLock); + expand_planner_arrays(root, num_children); - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) + foreach(l, inhOIDs) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); - - /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. - */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); - } - else - { - List *appinfos = NIL; + Oid childOID = lfirst_oid(l); + Relation newrelation; RangeTblEntry *childrte; Index childRTindex; + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. */ - foreach(l, inhOIDs) + if (RELATION_IS_OTHER_TEMP(newrelation)) { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = heap_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - heap_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - heap_close(newrelation, NoLock); + heap_close(newrelation, lockmode); + continue; } - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); + (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation, &childrte, + &childRTindex); + Assert(childrte != NULL); + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + Assert(childRTindex > 0); + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; } - heap_close(oldrelation, NoLock); + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } } /* * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) + Index parentRTindex, RelOptInfo *parentrel) { + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; - check_stack_depth(); - - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + expand_planner_arrays(root, bms_num_members(partindexes)); /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. */ - if (partdesc->nparts == 0) - { - parentrte->inh = false; - return; - } + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); - for (i = 0; i < partdesc->nparts; i++) + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) { - Oid childOID = partdesc->oids[i]; - Relation childrel; + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + RangeTblEntry *childrte; + Index childRTindex; - /* Open rel; we already have required locks */ - childrel = heap_open(childOID, NoLock); + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation, + &childrte, &childRTindex); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + + /* If the child is partitioned itself, expand it too. */ + if (childrel->part_scheme) + { + Assert(childrte->inh); expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); + childrel); + } + } - /* Close child relation, but keep locks */ - heap_close(childrel, NoLock); + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); } } /* - * expand_single_inheritance_child - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) + * The return value is the RelOptInfo that's added. * * PlanRowMarks still carry the top-parent's RTI, and the top-parent's * allMarkTypes field still accumulates values from all descendents. * * "parentrte" and "parentRTindex" are immediate parent's RTE and * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. */ -static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); Oid childOID = RelationGetRelid(childrel); RangeTblEntry *childrte; Index childRTindex; AppendRelInfo *appinfo; + RelOptInfo *childrelopt; /* * Build an RTE for the child, and attach to query's rangetable list. We * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh = false. Also, set requiredPerms to zero since - * all required permissions checks are done on the original RTE. Likewise, - * set the child's securityQuals to empty, because we only want to apply - * the parent's RLS conditions regardless of what RLS properties + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties * individual children may have. (This is an intentional choice to make * inherited RLS work like regular permissions checks.) The parent * securityQuals will be propagated to children along with other base @@ -1784,55 +1873,42 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childrte_p = childrte; childrte->relid = childOID; childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) - childrte->inh = true; - else - childrte->inh = false; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); childrte->requiredPerms = 0; childrte->securityQuals = NIL; parse->rtable = lappend(parse->rtable, childrte); childRTindex = list_length(parse->rtable); *childRTindex_p = childRTindex; - /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. - */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) - { - appinfo = makeNode(AppendRelInfo); - appinfo->parent_relid = parentRTindex; - appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->rd_rel->reltype; - appinfo->child_reltype = childrel->rd_rel->reltype; - make_inh_translation_list(parentrel, childrel, childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = parentOID; - *appinfos = lappend(*appinfos, appinfo); + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + childrel->rd_att, + childOID, + childrel->rd_rel->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); } /* @@ -1864,6 +1940,24 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need to create one, + * because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_append_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; } /* @@ -1874,14 +1968,11 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, * For paranoia's sake, we match type/collation as well as attribute name. */ static void -make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, - List **translated_vars) +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) { List *vars = NIL; - TupleDesc old_tupdesc = RelationGetDescr(oldrelation); - TupleDesc new_tupdesc = RelationGetDescr(newrelation); - Oid new_relid = RelationGetRelid(newrelation); int oldnatts = old_tupdesc->natts; int newnatts = new_tupdesc->natts; int old_attno; @@ -1911,7 +2002,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, * When we are generating the "translation list" for the parent table * of an inheritance set, no need to search for matches. */ - if (oldrelation == newrelation) + if (from_rel == to_rel) { vars = lappend(vars, makeVar(newvarno, (AttrNumber) (old_attno + 1), @@ -1937,10 +2028,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, { HeapTuple newtup; - newtup = SearchSysCacheAttName(new_relid, attname); + newtup = SearchSysCacheAttName(to_rel, attname); if (!newtup) elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; ReleaseSysCache(newtup); @@ -1950,10 +2041,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, /* Found it, check type and collation match */ if (atttypid != att->atttypid || atttypmod != att->atttypmod) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); if (attcollation != att->attcollation) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); vars = lappend(vars, makeVar(newvarno, (AttrNumber) (new_attno + 1), @@ -2020,6 +2111,255 @@ translate_col_privs(const Bitmapset *parent_privs, } /* + * build_append_child_rel + * Build a RelOptInfo for child relation of an append rel + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields based on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turn out to be self-contradictory, + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + List *childquals; + ListCell *lc; + bool have_const_false_cq; + Index cq_min_security; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = false; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, parent->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual = (Node *) rinfo->clause; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, childqual, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might + * have securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals.) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except + * that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* + * not likely that we'd see constants here, so no + * check + */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, + false, + false, + security_level, + NULL, NULL, + NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* Set child's version of baserestrictinfo. */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; +} + +AppendRelInfo * +make_append_rel_info(RelOptInfo *parentrel, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex) +{ + AppendRelInfo *appinfo = makeNode(AppendRelInfo); + + appinfo->parent_relid = parentrel->relid; + appinfo->child_relid = childRTindex; + appinfo->parent_reltype = parentrel->reltype; + appinfo->child_reltype = childreltype; + make_inh_translation_list(parentrel->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, + &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; + + return appinfo; +} + +/* * adjust_appendrel_attrs * Copy the specified query or expression and translate Vars referring to a * parent rel to refer to the corresponding child rel instead. We also diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile index c54d0a690d..da9ccf32b4 100644 --- a/src/backend/optimizer/util/Makefile +++ b/src/backend/optimizer/util/Makefile @@ -13,6 +13,6 @@ top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global OBJS = clauses.o joininfo.o orclauses.o pathnode.o placeholder.o \ - plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o + plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index a0fed5be42..bf441741e0 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -106,7 +106,7 @@ static void set_baserel_partition_key_exprs(Relation relation, */ void get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) + Bitmapset *updatedCols, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; @@ -444,11 +444,31 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, get_relation_foreign_keys(root, rel, relation, inhparent); /* - * Collect info about relation's partitioning scheme, if any. Only - * inheritance parents may be partitioned. + * Collect some additional information for inheritance parents. */ - if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - set_relation_partition_info(root, rel, relation); + if (inhparent) + { + /* + * We'll need the TupleDesc when initializing the child relation. + * A copy is being made because concurrent changes might drop + * the relcache entry. That's possible because ALTER TABLE + * child_table NO INHERIT parent_table only requires an + * AccessShareLock on parent_table. + */ + rel->tupdesc = CreateTupleDescCopy(RelationGetDescr(relation)); + rel->reltype = RelationGetForm(relation)->reltype; + + /* + * If partitioned, also save the information of partitioning scheme, + * and whether the query updates any of the partition key columns. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + set_relation_partition_info(root, rel, relation); + root->partColsUpdated |= has_partition_attrs(relation, updatedCols, + NULL); + } + } heap_close(relation, NoLock); @@ -1853,16 +1873,20 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; + + /* + * Since we must've taken a lock on the table, it's okay to simply copy + * the pointers to relcache data here. + */ + rel->part_oids = partdesc->oids; + rel->boundinfo = partdesc->boundinfo; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 29ba19349f..c23db9d78d 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -130,6 +130,37 @@ setup_append_rel_array(PlannerInfo *root) } } +/* Expand the PlannerInfo arrays by add_size members and zero-init it. */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size = root->simple_rel_array_size + add_size; + + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + if (root->append_rel_array) + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + else + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + + /* Set the contents of just allocated memory to 0. */ + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + root->simple_rel_array_size = new_size; +} + /* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. @@ -238,7 +269,8 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte->relid, rte->inh, rte->updatedCols, + rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -279,52 +311,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); - /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. - */ - if (rte->inh) - { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); - - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; - } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); - } - return rel; } diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 35c87535d3..fcf8d6032c 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -45,6 +45,7 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/planner.h" #include "optimizer/predtest.h" @@ -436,17 +437,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -540,23 +547,20 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ @@ -564,6 +568,13 @@ prune_append_rel_partitions(RelOptInfo *rel) return NULL; /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. */ @@ -590,15 +601,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 7f71f1c948..458be2769e 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -15,6 +15,7 @@ #define RELATION_H #include "access/sdir.h" +#include "access/tupdesc.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/params.h" @@ -726,6 +727,7 @@ typedef struct RelOptInfo int nparts; /* number of partitions */ struct PartitionBoundInfoData *boundinfo; /* Partition bounds */ List *partition_qual; /* partition constraint */ + Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ List **partexprs; /* Non-nullable partition key expressions. */ @@ -735,6 +737,12 @@ typedef struct RelOptInfo Index inh_root_parent; /* For otherrels, this is the RT index of * inheritance table mentioned in the query * from which this relation originated */ + /* + * Set only if this is an inheritance parent relation. This information + * is needed when initializing the planning info for children. + */ + TupleDesc tupdesc; /* A "copy" of the table's tuple desriptor */ + Oid reltype; /* Table's reltype */ } RelOptInfo; /* diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 81abcf53a8..b1baa3117a 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -261,6 +261,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index 7d53cbbb87..edaf2a3b4f 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -26,7 +26,7 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); + bool inhparent, Bitmapset *updatedCols, RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 38608770a2..faae07d240 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -38,7 +38,8 @@ extern Expr *canonicalize_qual(Expr *qual, bool is_check); /* * prototypes for preptlist.c */ -extern List *preprocess_targetlist(PlannerInfo *root); +extern List *preprocess_targetlist(PlannerInfo *root, + bool inheritance_expanded); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); @@ -51,7 +52,10 @@ extern void expand_inherited_tables(PlannerInfo *root); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); - +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parentrel, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex); extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, Relids child_relids, Relids top_parent_relids); @@ -64,5 +68,4 @@ extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, Relids left_relids, Relids right_relids); extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, Relids child_relids, Relids top_parent_relids); - #endif /* PREP_H */ diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h index e07aaaf798..ac0a979010 100644 --- a/src/include/partitioning/partprune.h +++ b/src/include/partitioning/partprune.h @@ -76,7 +76,7 @@ extern PartitionPruneInfo *make_partition_pruneinfo(PlannerInfo *root, List *subpaths, List *partitioned_rels, List *prunequal); -extern Relids prune_append_rel_partitions(RelOptInfo *rel); +extern Bitmapset *prune_append_rel_partitions(RelOptInfo *rel); extern Bitmapset *get_matching_partitions(PartitionPruneContext *context, List *pruning_steps); diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 6bc106831e..1450cef057 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) -- 2.11.0
v10-0004-Move-append-expansion-code-into-its-own-file.patch (text/plain, 112.7K) download From dc5c84daee7ce358c2115bbff2d2c5f3888dead1 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 7 Nov 2018 16:51:31 +0900 Subject: [PATCH v10 4/6] Move append expansion code into its own file This commit moves expand_append_rel and underlings currently in optimizer/prep/prepunionc.c to optimizer/utils/append.c. All of the AppendRelInfo based expression manipulation routines are moved to optimizer/utils/appendinfo.c. This commit only moves the code and contains no functional changes. --- src/backend/optimizer/path/allpaths.c | 2 + src/backend/optimizer/path/equivclass.c | 1 + src/backend/optimizer/path/joinrels.c | 1 + src/backend/optimizer/plan/planmain.c | 2 + src/backend/optimizer/plan/planner.c | 1 + src/backend/optimizer/prep/prepunion.c | 1573 ------------------------------- src/backend/optimizer/util/Makefile | 5 +- src/backend/optimizer/util/appendinfo.c | 851 +++++++++++++++++ src/backend/optimizer/util/inherit.c | 776 +++++++++++++++ src/backend/optimizer/util/pathnode.c | 1 + src/backend/optimizer/util/relnode.c | 1 + src/backend/partitioning/partprune.c | 1 + src/include/optimizer/appendinfo.h | 43 + src/include/optimizer/inherit.h | 25 + src/include/optimizer/prep.h | 19 - 15 files changed, 1708 insertions(+), 1594 deletions(-) create mode 100644 src/backend/optimizer/util/appendinfo.c create mode 100644 src/backend/optimizer/util/inherit.c create mode 100644 src/include/optimizer/appendinfo.h create mode 100644 src/include/optimizer/inherit.h diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index e8f9463afb..927f54c694 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -30,6 +30,8 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "optimizer/inherit.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/geqo.h" diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 029665b974..f52adc72d0 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -22,6 +22,7 @@ #include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index da0831de4e..6e321ec9e7 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -15,6 +15,7 @@ #include "postgres.h" #include "miscadmin.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index be261061f2..9eb140b204 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -20,7 +20,9 @@ */ #include "postgres.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" +#include "optimizer/inherit.h" #include "optimizer/orclauses.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 148032342f..85379a6c7d 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -39,6 +39,7 @@ #include "nodes/print.h" #endif #include "nodes/relation.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 303554de34..d539326015 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -12,11 +12,6 @@ * case, but most of the heavy lifting for that is done elsewhere, * notably in prepjointree.c and allpaths.c. * - * There is also some code here to support planning of queries that use - * inheritance (SELECT FROM foo*). Inheritance trees are converted into - * append relations, and thenceforth share code with the UNION ALL case. - * - * * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -39,32 +34,21 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" -#include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" -#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" -#include "optimizer/var.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/selfuncs.h" #include "utils/syscache.h" -typedef struct -{ - PlannerInfo *root; - int nappinfos; - AppendRelInfo **appinfos; -} adjust_appendrel_attrs_context; - static RelOptInfo *recurse_set_operations(Node *setOp, PlannerInfo *root, List *colTypes, List *colCollations, bool junkOK, @@ -103,34 +87,6 @@ static List *generate_append_tlist(List *colTypes, List *colCollations, List *input_tlists, List *refnames_tlist); static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist); -static void expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, - RangeTblEntry *rte, Index rti); -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti, RelOptInfo *rel); -static void expand_partitioned_rtentry(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel); -static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel, - PlanRowMark *top_parentrc, Relation childrel, - RangeTblEntry **childrte_p, Index *childRTindex_p); -static void make_inh_translation_list(TupleDesc old_tupdesc, - TupleDesc new_tupdesc, - Oid from_rel, Oid to_rel, - Index newvarno, List **translated_vars); -static RelOptInfo *build_append_child_rel(PlannerInfo *root, - RelOptInfo *parent, - Index childRTindex); -static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); -static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars); -static Node *adjust_appendrel_attrs_mutator(Node *node, - adjust_appendrel_attrs_context *context); -static Relids adjust_child_relids(Relids relids, int nappinfos, - AppendRelInfo **appinfos); -static List *adjust_inherited_tlist(List *tlist, - AppendRelInfo *context); /* @@ -1467,1532 +1423,3 @@ generate_setop_grouplist(SetOperationStmt *op, List *targetlist) Assert(lg == NULL); return grouplist; } - - -/* - * expand_inherited_tables - * Expand each rangetable entry that represents an inheritance set - * into an "append relation". At the conclusion of this process, - * the "inh" flag is set in all and only those RTEs that are append - * relation parents. - */ -void -expand_inherited_tables(PlannerInfo *root) -{ - int orig_rtable_size; - Index rti; - - Assert(root->simple_rel_array_size > 0); - orig_rtable_size = root->simple_rel_array_size; - - /* - * expand_append_rtentry may add RTEs to parse->rtable. The function is - * expected to recursively handle any RTEs that it creates with inh=true. - * So just scan as far as the original end of the rtable list. - */ - for (rti = 1; rti < orig_rtable_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - RangeTblEntry *rte = root->simple_rte_array[rti]; - - /* there may be empty slots corresponding to non-baserel RTEs */ - if (brel == NULL) - continue; - - if (rte->inh) - expand_append_rtentry(root, brel, rte, rti); - } -} - -/* - * expand_append_rtentry - * This initializes RelOptInfos for an appendrel's child relations, if - * any - * - * 'rel' is the appendrel parent, whose range table entry ('rte') has been - * marked to require adding children. An appendrel parent could either - * be a subquery (if we flattened UNION ALL query) or a table that's known - * to have inheritance children. The latter consists of both regular - * inheritance parents and partitioned tables. - * - * For a subquery parent, there is not much to be done here because the - * children's RTEs are already present in the query, so we just initialize - * RelOptInfos for them. Also, the AppendRelInfos for child subqueries - * have already been added. - * - * For tables, we need to add the children to the range table and initialize - * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For - * a partitioned parent, we only add the children remaining after pruning. - * For regular inheritance parents, we find the children using - * find_all_inheritors and add all of them. - * - * If it turns out that there are no children, then we set rte->inh to false - * to let the caller know that only the parent table needs to be scanned. The - * caller can accordingly switch to a non-Append path. For a partitioned - * parent, that means an empty relation because parents themselves contain no - * data. - * - * For the regular inheritance case, the parent also gets another RTE with - * inh = false to represent it as an appendrel child. The original RTE is - * considered to represent the whole inheritance set. - */ -static void -expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, - Index rti) -{ - Assert(rte->inh); - /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ - Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); - - /* - * UNION ALL children already got RTEs and AppendRelInfos, so just build - * RelOptInfos and return. - * - * It might be a bit odd that this code is in this, because there is - * nothing to expand really. - */ - if (rte->rtekind == RTE_SUBQUERY) - { - ListCell *l; - - /* - * We don't need to use expand_planner_arrays in this case, because - * no new child RTEs are created. setup_simple_rel_arrays() and - * setup_append_rel_array would've considered these child RTEs when - * allocating space for various arrays. - */ - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst(l); - Index childRTindex = appinfo->child_relid; - - if (appinfo->parent_relid != rti) - continue; - - Assert(childRTindex < root->simple_rel_array_size); - Assert(root->simple_rte_array[childRTindex] != NULL); - - /* - * We set the correct value of baserestricinfo and - * baserestrict_min_security below. - */ - root->simple_rel_array[childRTindex] = - build_append_child_rel(root, rel, appinfo->child_relid); - } - } - else - { - Assert(rte->rtekind == RTE_RELATION); - Assert(has_subclass(rte->relid)); - - /* - * The rewriter should already have obtained an appropriate lock on - * each relation named in the query. However, for each child relation - * we add to the query, we must obtain an appropriate lock, because - * this will be the first use of those relations in the - * parse/rewrite/plan pipeline. Child rels should use the same - * lockmode as their parent. - */ - Assert(rte->rellockmode != NoLock); - - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - expand_partitioned_rtentry(root, rte, rti, rel); - else - expand_inherited_rtentry(root, rte, rti, rel); - } -} - -/* - * expand_inherited_rtentry - * Add entries for all the child tables to the query's rangetable, and - * build AppendRelInfo nodes for all the child tables and add them to - * root->append_rel_list. - * - * Note that the original RTE is considered to represent the whole - * inheritance set. The first of the generated RTEs is an RTE for the same - * table, but with inh = false, to represent the parent table in its role - * as a simple member of the inheritance set. - * - * A childless table is never considered to be an inheritance set. For - * regular inheritance, a parent RTE must always have at least two associated - * AppendRelInfos: one corresponding to the parent table as a simple member of - * inheritance set and one or more corresponding to the actual children. - */ -static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, - RelOptInfo *rel) -{ - Oid parentOID; - PlanRowMark *oldrc; - LOCKMODE lockmode = rte->rellockmode; - List *inhOIDs; - ListCell *l; - int num_children; - int num_children_added = 0; - - Assert(rte->rtekind == RTE_RELATION); - Assert(lockmode != NoLock); - parentOID = rte->relid; - - /* Scan for all members of inheritance set, acquire needed locks */ - inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); - - /* - * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite has_subclass() check performed by - * subquery_planner, if table once had a child but no longer does. - */ - num_children = list_length(inhOIDs); - if (num_children < 2) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks - * should've set isParent = true. We'll generate a new PlanRowMark for - * each child. - */ - oldrc = get_plan_rowmark(root->rowMarks, rti); - Assert(oldrc == NULL || oldrc->isParent); - - /* - * Must expand PlannerInfo arrays by num_children before we can add - * children. - */ - expand_planner_arrays(root, num_children); - - foreach(l, inhOIDs) - { - Oid childOID = lfirst_oid(l); - Relation newrelation; - RangeTblEntry *childrte; - Index childRTindex; - - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (RELATION_IS_OTHER_TEMP(newrelation)) - { - heap_close(newrelation, lockmode); - continue; - } - - (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, - newrelation, &childrte, - &childRTindex); - Assert(childrte != NULL); - /* All regular inheritance children are leaf children. */ - Assert(!childrte->inh); - Assert(childRTindex > 0); - - /* Close child relations, but keep locks */ - heap_close(newrelation, NoLock); - num_children_added++; - } - - /* - * If all children, including the parent (as child rel), were - * excluded, mark the parent rel as empty. If all the children were temp - * tables, pretend it's a non-inheritance situation; we don't need Append - * node in that case. The duplicate RTE we added for the parent table is - * harmless, so we don't bother to get rid of it; ditto for the useless - * PlanRowMark node. - */ - if (num_children_added == 0) - mark_dummy_rel(rel); - else if (num_children_added == 1) - rte->inh = false; - - /* - * Add junk columns needed by the row mark if any and also add the - * relevant expressions to the root parent's reltarget. - */ - if (oldrc) - { - List *tlist = add_rowmark_junk_columns(root, oldrc); - - build_base_rel_tlists(root, tlist); - } -} - -/* - * expand_partitioned_rtentry - * Prunes unnecessary partitions of a partitioned table and adds - * remaining ones to the Query and the PlannerInfo - * - * Partitions are added to the query in order in which they are found in - * the parent's PartitionDesc. - * - * Note: even though only the unpruned partitions will be added to the - * resulting plan, this still locks *all* partitions via find_all_inheritors - * when this function is called for the root partitioned table. - */ -static void -expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel) -{ - LOCKMODE lockmode = parentrte->rellockmode; - PlanRowMark *rootrc = NULL; - int i; - Bitmapset *partindexes; - Index rootParentRTindex = parentrel->inh_root_parent > 0 ? - parentrel->inh_root_parent : - parentRTindex; - - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - - /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, values of the - * indexes of partitioned relations that appear down in the tree will be - * bubbled up into root parent's list so that when we've created Paths for - * all the children, the root table's list will contain all such indexes. - */ - parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - - /* Perform pruning. */ - partindexes = prune_append_rel_partitions(parentrel); - - /* Must expand PlannerInfo arrays before we can add children. */ - expand_planner_arrays(root, bms_num_members(partindexes)); - - /* - * For partitioned tables, we also store the partition RelOptInfo - * pointers in the parent's RelOptInfo. - */ - parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * - parentrel->nparts); - - rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); - Assert(rootrc == NULL || rootrc->isParent); - i = -1; - while ((i = bms_next_member(partindexes, i)) >= 0) - { - Oid childOID = parentrel->part_oids[i]; - Relation newrelation; - RelOptInfo *childrel; - RangeTblEntry *childrte; - Index childRTindex; - - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); - Assert(!RELATION_IS_OTHER_TEMP(newrelation)); - - /* - * A partitioned child table with 0 children is a dummy rel, so don't - * bother creating planner objects for it. - */ - if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && - RelationGetPartitionDesc(newrelation)->nparts == 0) - { - heap_close(newrelation, NoLock); - continue; - } - - childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, - parentrel, rootrc, newrelation, - &childrte, &childRTindex); - Assert(childrel != NULL); - parentrel->part_rels[i] = childrel; - - /* Close child relations, but keep locks */ - heap_close(newrelation, NoLock); - - /* If the child is partitioned itself, expand it too. */ - if (childrel->part_scheme) - { - Assert(childrte->inh); - expand_partitioned_rtentry(root, childrte, childRTindex, - childrel); - } - } - - /* - * Add junk columns needed by the row mark if any and also add the - * relevant expressions to the root parent's reltarget. - */ - if (rootrc) - { - List *tlist = add_rowmark_junk_columns(root, rootrc); - - build_base_rel_tlists(root, tlist); - } -} - -/* - * add_inheritance_child_rel - * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally - * a RelOptInfo for an inheritance child relation. - * - * The return value is the RelOptInfo that's added. - * - * PlanRowMarks still carry the top-parent's RTI, and the top-parent's - * allMarkTypes field still accumulates values from all descendents. - * - * "parentrte" and "parentRTindex" are immediate parent's RTE and - * RTI. "top_parentrc" is top parent's PlanRowMark. - */ -static RelOptInfo * -add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel, - PlanRowMark *top_parentrc, Relation childrel, - RangeTblEntry **childrte_p, Index *childRTindex_p) -{ - Query *parse = root->parse; - Oid childOID = RelationGetRelid(childrel); - RangeTblEntry *childrte; - Index childRTindex; - AppendRelInfo *appinfo; - RelOptInfo *childrelopt; - - /* - * Build an RTE for the child, and attach to query's rangetable list. We - * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh appropriately. Also, set requiredPerms to zero - * since all required permissions checks are done on the original RTE. - * Likewise, set the child's securityQuals to empty, because we only want - * to apply the parent's RLS conditions regardless of what RLS properties - * individual children may have. (This is an intentional choice to make - * inherited RLS work like regular permissions checks.) The parent - * securityQuals will be propagated to children along with other base - * restriction clauses, so we don't need to do it here. - */ - childrte = copyObject(parentrte); - *childrte_p = childrte; - childrte->relid = childOID; - childrte->relkind = childrel->rd_rel->relkind; - /* - * A partitioned child will need to be expanded as an append parent - * itself, so set its inh to true. - */ - childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); - childrte->requiredPerms = 0; - childrte->securityQuals = NIL; - parse->rtable = lappend(parse->rtable, childrte); - childRTindex = list_length(parse->rtable); - *childRTindex_p = childRTindex; - - /* Create an AppendRelInfo and add it to planner's global list. */ - appinfo = make_append_rel_info(parentrel, parentrte, - childrel->rd_att, - childOID, - childrel->rd_rel->reltype, - childRTindex); - root->append_rel_list = lappend(root->append_rel_list, appinfo); - - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childrte->relid != parentrte->relid) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } - - /* - * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE. - */ - if (top_parentrc) - { - PlanRowMark *childrc = makeNode(PlanRowMark); - - childrc->rti = childRTindex; - childrc->prti = top_parentrc->rti; - childrc->rowmarkId = top_parentrc->rowmarkId; - /* Reselect rowmark type, because relkind might not match parent */ - childrc->markType = select_rowmark_type(childrte, - top_parentrc->strength); - childrc->allMarkTypes = (1 << childrc->markType); - childrc->strength = top_parentrc->strength; - childrc->waitPolicy = top_parentrc->waitPolicy; - - /* - * We mark RowMarks for partitioned child tables as parent RowMarks so - * that the executor ignores them (except their existence means that - * the child tables be locked using appropriate mode). - */ - childrc->isParent = (childrte->relkind == RELKIND_PARTITIONED_TABLE); - - /* Include child's rowmark type in top parent's allMarkTypes */ - top_parentrc->allMarkTypes |= childrc->allMarkTypes; - - root->rowMarks = lappend(root->rowMarks, childrc); - } - - /* - * Add the RelOptInfo. Even though we may not really scan this relation - * for reasons such as contradictory quals, we still need to create one, - * because for every RTE in the query's range table, there must be an - * accompanying RelOptInfo. - */ - - /* First, store the RTE and appinfos into planner arrays. */ - Assert(root->simple_rte_array[childRTindex] == NULL); - root->simple_rte_array[childRTindex] = childrte; - Assert(root->append_rel_array[childRTindex] == NULL); - root->append_rel_array[childRTindex] = appinfo; - - childrelopt = build_append_child_rel(root, parentrel, childRTindex); - Assert(childrelopt != NULL); - - return childrelopt; -} - -/* - * make_inh_translation_list - * Build the list of translations from parent Vars to child Vars for - * an inheritance child. - * - * For paranoia's sake, we match type/collation as well as attribute name. - */ -static void -make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, - Oid from_rel, Oid to_rel, - Index newvarno, List **translated_vars) -{ - List *vars = NIL; - int oldnatts = old_tupdesc->natts; - int newnatts = new_tupdesc->natts; - int old_attno; - int new_attno = 0; - - for (old_attno = 0; old_attno < oldnatts; old_attno++) - { - Form_pg_attribute att; - char *attname; - Oid atttypid; - int32 atttypmod; - Oid attcollation; - - att = TupleDescAttr(old_tupdesc, old_attno); - if (att->attisdropped) - { - /* Just put NULL into this list entry */ - vars = lappend(vars, NULL); - continue; - } - attname = NameStr(att->attname); - atttypid = att->atttypid; - atttypmod = att->atttypmod; - attcollation = att->attcollation; - - /* - * When we are generating the "translation list" for the parent table - * of an inheritance set, no need to search for matches. - */ - if (from_rel == to_rel) - { - vars = lappend(vars, makeVar(newvarno, - (AttrNumber) (old_attno + 1), - atttypid, - atttypmod, - attcollation, - 0)); - continue; - } - - /* - * Otherwise we have to search for the matching column by name. - * There's no guarantee it'll have the same column position, because - * of cases like ALTER TABLE ADD COLUMN and multiple inheritance. - * However, in simple cases, the relative order of columns is mostly - * the same in both relations, so try the column of newrelation that - * follows immediately after the one that we just found, and if that - * fails, let syscache handle it. - */ - if (new_attno >= newnatts || - (att = TupleDescAttr(new_tupdesc, new_attno))->attisdropped || - strcmp(attname, NameStr(att->attname)) != 0) - { - HeapTuple newtup; - - newtup = SearchSysCacheAttName(to_rel, attname); - if (!newtup) - elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, get_rel_name(to_rel)); - new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; - ReleaseSysCache(newtup); - - att = TupleDescAttr(new_tupdesc, new_attno); - } - - /* Found it, check type and collation match */ - if (atttypid != att->atttypid || atttypmod != att->atttypmod) - elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, get_rel_name(to_rel)); - if (attcollation != att->attcollation) - elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, get_rel_name(to_rel)); - - vars = lappend(vars, makeVar(newvarno, - (AttrNumber) (new_attno + 1), - atttypid, - atttypmod, - attcollation, - 0)); - new_attno++; - } - - *translated_vars = vars; -} - -/* - * translate_col_privs - * Translate a bitmapset representing per-column privileges from the - * parent rel's attribute numbering to the child's. - * - * The only surprise here is that we don't translate a parent whole-row - * reference into a child whole-row reference. That would mean requiring - * permissions on all child columns, which is overly strict, since the - * query is really only going to reference the inherited columns. Instead - * we set the per-column bits for all inherited columns. - */ -static Bitmapset * -translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars) -{ - Bitmapset *child_privs = NULL; - bool whole_row; - int attno; - ListCell *lc; - - /* System attributes have the same numbers in all tables */ - for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) - { - if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - attno - FirstLowInvalidHeapAttributeNumber); - } - - /* Check if parent has whole-row reference */ - whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, - parent_privs); - - /* And now translate the regular user attributes, using the vars list */ - attno = InvalidAttrNumber; - foreach(lc, translated_vars) - { - Var *var = lfirst_node(Var, lc); - - attno++; - if (var == NULL) /* ignore dropped columns */ - continue; - if (whole_row || - bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - var->varattno - FirstLowInvalidHeapAttributeNumber); - } - - return child_privs; -} - -/* - * build_append_child_rel - * Build a RelOptInfo for child relation of an append rel - * - * After creating the RelOptInfo for the given child RT index, it goes on to - * initialize some of its fields based on the parent RelOptInfo. - * - * If the quals in baserestrictinfo turn out to be self-contradictory, - * RelOptInfo is marked dummy before returning. - */ -static RelOptInfo * -build_append_child_rel(PlannerInfo *root, - RelOptInfo *parent, - Index childRTindex) -{ - RelOptInfo *childrel; - RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; - AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; - List *childquals; - ListCell *lc; - bool have_const_false_cq; - Index cq_min_security; - - /* Build the RelOptInfo. */ - childrel = build_simple_rel(root, childRTindex, parent); - - /* - * Propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - */ - childrel->direct_lateral_relids = parent->direct_lateral_relids; - childrel->lateral_relids = parent->lateral_relids; - childrel->lateral_referencers = parent->lateral_referencers; - - /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) - */ - childquals = false; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, parent->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual = (Node *) rinfo->clause; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, childqual, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might - * have securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals.) Pull any such securityQuals up into the - * baserestrictinfo for the child. This is similar to - * process_security_barrier_quals() for the parent rel, except - * that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* - * not likely that we'd see constants here, so no - * check - */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, - false, - false, - security_level, - NULL, NULL, - NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* Set child's version of baserestrictinfo. */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - } - - return childrel; -} - -/* - * add_rowmark_junk_columns - * Add necessary junk columns for rowmarked inheritance parent rel. - * - * These values are needed for locking of rels selected FOR UPDATE/SHARE, and - * to do EvalPlanQual rechecking. See comments for PlanRowMark in - * plannodes.h. - */ -static List * -add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) -{ - List *tlist = root->processed_tlist; - Var *var; - char resname[32]; - TargetEntry *tle; - - if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) - { - /* Need to fetch TID */ - var = makeVar(rc->rti, - SelfItemPointerAttributeNumber, - TIDOID, - -1, - InvalidOid, - 0); - snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - } - if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) - { - /* Need the whole row as a junk var */ - var = makeWholeRowVar(root->simple_rte_array[rc->rti], - rc->rti, - 0, - false); - snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - } - - /* For inheritance cases, always fetch the tableoid too. */ - var = makeVar(rc->rti, - TableOidAttributeNumber, - OIDOID, - -1, - InvalidOid, - 0); - snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - - return tlist; -} - -AppendRelInfo * -make_append_rel_info(RelOptInfo *parentrel, RangeTblEntry *parentrte, - TupleDesc childdesc, Oid childoid, Oid childreltype, - Index childRTindex) -{ - AppendRelInfo *appinfo = makeNode(AppendRelInfo); - - appinfo->parent_relid = parentrel->relid; - appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->reltype; - appinfo->child_reltype = childreltype; - make_inh_translation_list(parentrel->tupdesc, childdesc, - parentrte->relid, childoid, - childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = parentrte->relid; - - return appinfo; -} - -/* - * adjust_appendrel_attrs - * Copy the specified query or expression and translate Vars referring to a - * parent rel to refer to the corresponding child rel instead. We also - * update rtindexes appearing outside Vars, such as resultRelation and - * jointree relids. - * - * Note: this is only applied after conversion of sublinks to subplans, - * so we don't need to cope with recursion into sub-queries. - * - * Note: this is not hugely different from what pullup_replace_vars() does; - * maybe we should try to fold the two routines together. - */ -Node * -adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, - AppendRelInfo **appinfos) -{ - Node *result; - adjust_appendrel_attrs_context context; - - context.root = root; - context.nappinfos = nappinfos; - context.appinfos = appinfos; - - /* If there's nothing to adjust, don't call this function. */ - Assert(nappinfos >= 1 && appinfos != NULL); - - /* - * Must be prepared to start with a Query or a bare expression tree. - */ - if (node && IsA(node, Query)) - { - Query *newnode; - int cnt; - - newnode = query_tree_mutator((Query *) node, - adjust_appendrel_attrs_mutator, - (void *) &context, - QTW_IGNORE_RC_SUBQUERIES); - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (newnode->resultRelation == appinfo->parent_relid) - { - newnode->resultRelation = appinfo->child_relid; - /* Fix tlist resnos too, if it's inherited UPDATE */ - if (newnode->commandType == CMD_UPDATE) - newnode->targetList = - adjust_inherited_tlist(newnode->targetList, - appinfo); - break; - } - } - - result = (Node *) newnode; - } - else - result = adjust_appendrel_attrs_mutator(node, &context); - - return result; -} - -static Node * -adjust_appendrel_attrs_mutator(Node *node, - adjust_appendrel_attrs_context *context) -{ - AppendRelInfo **appinfos = context->appinfos; - int nappinfos = context->nappinfos; - int cnt; - - if (node == NULL) - return NULL; - if (IsA(node, Var)) - { - Var *var = (Var *) copyObject(node); - AppendRelInfo *appinfo = NULL; - - for (cnt = 0; cnt < nappinfos; cnt++) - { - if (var->varno == appinfos[cnt]->parent_relid) - { - appinfo = appinfos[cnt]; - break; - } - } - - if (var->varlevelsup == 0 && appinfo) - { - var->varno = appinfo->child_relid; - var->varnoold = appinfo->child_relid; - if (var->varattno > 0) - { - Node *newnode; - - if (var->varattno > list_length(appinfo->translated_vars)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - var->varattno, get_rel_name(appinfo->parent_reloid)); - newnode = copyObject(list_nth(appinfo->translated_vars, - var->varattno - 1)); - if (newnode == NULL) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - var->varattno, get_rel_name(appinfo->parent_reloid)); - return newnode; - } - else if (var->varattno == 0) - { - /* - * Whole-row Var: if we are dealing with named rowtypes, we - * can use a whole-row Var for the child table plus a coercion - * step to convert the tuple layout to the parent's rowtype. - * Otherwise we have to generate a RowExpr. - */ - if (OidIsValid(appinfo->child_reltype)) - { - Assert(var->vartype == appinfo->parent_reltype); - if (appinfo->parent_reltype != appinfo->child_reltype) - { - ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr); - - r->arg = (Expr *) var; - r->resulttype = appinfo->parent_reltype; - r->convertformat = COERCE_IMPLICIT_CAST; - r->location = -1; - /* Make sure the Var node has the right type ID, too */ - var->vartype = appinfo->child_reltype; - return (Node *) r; - } - } - else - { - /* - * Build a RowExpr containing the translated variables. - * - * In practice var->vartype will always be RECORDOID here, - * so we need to come up with some suitable column names. - * We use the parent RTE's column names. - * - * Note: we can't get here for inheritance cases, so there - * is no need to worry that translated_vars might contain - * some dummy NULLs. - */ - RowExpr *rowexpr; - List *fields; - RangeTblEntry *rte; - - rte = rt_fetch(appinfo->parent_relid, - context->root->parse->rtable); - fields = copyObject(appinfo->translated_vars); - rowexpr = makeNode(RowExpr); - rowexpr->args = fields; - rowexpr->row_typeid = var->vartype; - rowexpr->row_format = COERCE_IMPLICIT_CAST; - rowexpr->colnames = copyObject(rte->eref->colnames); - rowexpr->location = -1; - - return (Node *) rowexpr; - } - } - /* system attributes don't need any other translation */ - } - return (Node *) var; - } - if (IsA(node, CurrentOfExpr)) - { - CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node); - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (cexpr->cvarno == appinfo->parent_relid) - { - cexpr->cvarno = appinfo->child_relid; - break; - } - } - return (Node *) cexpr; - } - if (IsA(node, RangeTblRef)) - { - RangeTblRef *rtr = (RangeTblRef *) copyObject(node); - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (rtr->rtindex == appinfo->parent_relid) - { - rtr->rtindex = appinfo->child_relid; - break; - } - } - return (Node *) rtr; - } - if (IsA(node, JoinExpr)) - { - /* Copy the JoinExpr node with correct mutation of subnodes */ - JoinExpr *j; - AppendRelInfo *appinfo; - - j = (JoinExpr *) expression_tree_mutator(node, - adjust_appendrel_attrs_mutator, - (void *) context); - /* now fix JoinExpr's rtindex (probably never happens) */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - appinfo = appinfos[cnt]; - - if (j->rtindex == appinfo->parent_relid) - { - j->rtindex = appinfo->child_relid; - break; - } - } - return (Node *) j; - } - if (IsA(node, PlaceHolderVar)) - { - /* Copy the PlaceHolderVar node with correct mutation of subnodes */ - PlaceHolderVar *phv; - - phv = (PlaceHolderVar *) expression_tree_mutator(node, - adjust_appendrel_attrs_mutator, - (void *) context); - /* now fix PlaceHolderVar's relid sets */ - if (phv->phlevelsup == 0) - phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos, - context->appinfos); - return (Node *) phv; - } - - /* - * This is needed, because inheritance_make_rel_from_joinlist needs to - * translate root->join_info_list executing make_rel_from_joinlist for a - * given child. - */ - if (IsA(node, SpecialJoinInfo)) - { - SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; - SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); - - memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); - newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, - context->nappinfos, - context->appinfos); - newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, - context->nappinfos, - context->appinfos); - newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, - context->nappinfos, - context->appinfos); - newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, - context->nappinfos, - context->appinfos); - newinfo->semi_rhs_exprs = - (List *) expression_tree_mutator((Node *) - oldinfo->semi_rhs_exprs, - adjust_appendrel_attrs_mutator, - (void *) context); - return (Node *) newinfo; - } - - /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, AppendRelInfo)); - Assert(!IsA(node, PlaceHolderInfo)); - Assert(!IsA(node, MinMaxAggInfo)); - - /* - * We have to process RestrictInfo nodes specially. (Note: although - * set_append_rel_pathlist will hide RestrictInfos in the parent's - * baserestrictinfo list from us, it doesn't hide those in joininfo.) - */ - if (IsA(node, RestrictInfo)) - { - RestrictInfo *oldinfo = (RestrictInfo *) node; - RestrictInfo *newinfo = makeNode(RestrictInfo); - - /* Copy all flat-copiable fields */ - memcpy(newinfo, oldinfo, sizeof(RestrictInfo)); - - /* Recursively fix the clause itself */ - newinfo->clause = (Expr *) - adjust_appendrel_attrs_mutator((Node *) oldinfo->clause, context); - - /* and the modified version, if an OR clause */ - newinfo->orclause = (Expr *) - adjust_appendrel_attrs_mutator((Node *) oldinfo->orclause, context); - - /* adjust relid sets too */ - newinfo->clause_relids = adjust_child_relids(oldinfo->clause_relids, - context->nappinfos, - context->appinfos); - newinfo->required_relids = adjust_child_relids(oldinfo->required_relids, - context->nappinfos, - context->appinfos); - newinfo->outer_relids = adjust_child_relids(oldinfo->outer_relids, - context->nappinfos, - context->appinfos); - newinfo->nullable_relids = adjust_child_relids(oldinfo->nullable_relids, - context->nappinfos, - context->appinfos); - newinfo->left_relids = adjust_child_relids(oldinfo->left_relids, - context->nappinfos, - context->appinfos); - newinfo->right_relids = adjust_child_relids(oldinfo->right_relids, - context->nappinfos, - context->appinfos); - - /* - * Reset cached derivative fields, since these might need to have - * different values when considering the child relation. Note we - * don't reset left_ec/right_ec: each child variable is implicitly - * equivalent to its parent, so still a member of the same EC if any. - */ - newinfo->eval_cost.startup = -1; - newinfo->norm_selec = -1; - newinfo->outer_selec = -1; - newinfo->left_em = NULL; - newinfo->right_em = NULL; - newinfo->scansel_cache = NIL; - newinfo->left_bucketsize = -1; - newinfo->right_bucketsize = -1; - newinfo->left_mcvfreq = -1; - newinfo->right_mcvfreq = -1; - - return (Node *) newinfo; - } - - /* - * NOTE: we do not need to recurse into sublinks, because they should - * already have been converted to subplans before we see them. - */ - Assert(!IsA(node, SubLink)); - Assert(!IsA(node, Query)); - - return expression_tree_mutator(node, adjust_appendrel_attrs_mutator, - (void *) context); -} - -/* - * Substitute child relids for parent relids in a Relid set. The array of - * appinfos specifies the substitutions to be performed. - */ -static Relids -adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos) -{ - Bitmapset *result = NULL; - int cnt; - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - /* Remove parent, add child */ - if (bms_is_member(appinfo->parent_relid, relids)) - { - /* Make a copy if we are changing the set. */ - if (!result) - result = bms_copy(relids); - - result = bms_del_member(result, appinfo->parent_relid); - result = bms_add_member(result, appinfo->child_relid); - } - } - - /* If we made any changes, return the modified copy. */ - if (result) - return result; - - /* Otherwise, return the original set without modification. */ - return relids; -} - -/* - * Replace any relid present in top_parent_relids with its child in - * child_relids. Members of child_relids can be multiple levels below top - * parent in the partition hierarchy. - */ -Relids -adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, - Relids child_relids, Relids top_parent_relids) -{ - AppendRelInfo **appinfos; - int nappinfos; - Relids parent_relids = NULL; - Relids result; - Relids tmp_result = NULL; - int cnt; - - /* - * If the given relids set doesn't contain any of the top parent relids, - * it will remain unchanged. - */ - if (!bms_overlap(relids, top_parent_relids)) - return relids; - - appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); - - /* Construct relids set for the immediate parent of the given child. */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); - } - - /* Recurse if immediate parent is not the top parent. */ - if (!bms_equal(parent_relids, top_parent_relids)) - { - tmp_result = adjust_child_relids_multilevel(root, relids, - parent_relids, - top_parent_relids); - relids = tmp_result; - } - - result = adjust_child_relids(relids, nappinfos, appinfos); - - /* Free memory consumed by any intermediate result. */ - if (tmp_result) - bms_free(tmp_result); - bms_free(parent_relids); - pfree(appinfos); - - return result; -} - -/* - * Adjust the targetlist entries of an inherited UPDATE operation - * - * The expressions have already been fixed, but we have to make sure that - * the target resnos match the child table (they may not, in the case of - * a column that was added after-the-fact by ALTER TABLE). In some cases - * this can force us to re-order the tlist to preserve resno ordering. - * (We do all this work in special cases so that preptlist.c is fast for - * the typical case.) - * - * The given tlist has already been through expression_tree_mutator; - * therefore the TargetEntry nodes are fresh copies that it's okay to - * scribble on. - * - * Note that this is not needed for INSERT because INSERT isn't inheritable. - */ -static List * -adjust_inherited_tlist(List *tlist, AppendRelInfo *context) -{ - bool changed_it = false; - ListCell *tl; - List *new_tlist; - bool more; - int attrno; - - /* This should only happen for an inheritance case, not UNION ALL */ - Assert(OidIsValid(context->parent_reloid)); - - /* Scan tlist and update resnos to match attnums of child rel */ - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - Var *childvar; - - if (tle->resjunk) - continue; /* ignore junk items */ - - /* Look up the translation of this column: it must be a Var */ - if (tle->resno <= 0 || - tle->resno > list_length(context->translated_vars)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - tle->resno, get_rel_name(context->parent_reloid)); - childvar = (Var *) list_nth(context->translated_vars, tle->resno - 1); - if (childvar == NULL || !IsA(childvar, Var)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - tle->resno, get_rel_name(context->parent_reloid)); - - if (tle->resno != childvar->varattno) - { - tle->resno = childvar->varattno; - changed_it = true; - } - } - - /* - * If we changed anything, re-sort the tlist by resno, and make sure - * resjunk entries have resnos above the last real resno. The sort - * algorithm is a bit stupid, but for such a seldom-taken path, small is - * probably better than fast. - */ - if (!changed_it) - return tlist; - - new_tlist = NIL; - more = true; - for (attrno = 1; more; attrno++) - { - more = false; - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - - if (tle->resjunk) - continue; /* ignore junk items */ - - if (tle->resno == attrno) - new_tlist = lappend(new_tlist, tle); - else if (tle->resno > attrno) - more = true; - } - } - - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - - if (!tle->resjunk) - continue; /* here, ignore non-junk items */ - - tle->resno = attrno; - new_tlist = lappend(new_tlist, tle); - attrno++; - } - - return new_tlist; -} - -/* - * adjust_appendrel_attrs_multilevel - * Apply Var translations from a toplevel appendrel parent down to a child. - * - * In some cases we need to translate expressions referencing a parent relation - * to reference an appendrel child that's multiple levels removed from it. - */ -Node * -adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, - Relids child_relids, - Relids top_parent_relids) -{ - AppendRelInfo **appinfos; - Bitmapset *parent_relids = NULL; - int nappinfos; - int cnt; - - Assert(bms_num_members(child_relids) == bms_num_members(top_parent_relids)); - - appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); - - /* Construct relids set for the immediate parent of given child. */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); - } - - /* Recurse if immediate parent is not the top parent. */ - if (!bms_equal(parent_relids, top_parent_relids)) - node = adjust_appendrel_attrs_multilevel(root, node, parent_relids, - top_parent_relids); - - /* Now translate for this child */ - node = adjust_appendrel_attrs(root, node, nappinfos, appinfos); - - pfree(appinfos); - - return node; -} - -/* - * Construct the SpecialJoinInfo for a child-join by translating - * SpecialJoinInfo for the join between parents. left_relids and right_relids - * are the relids of left and right side of the join respectively. - */ -SpecialJoinInfo * -build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, - Relids left_relids, Relids right_relids) -{ - SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo); - AppendRelInfo **left_appinfos; - int left_nappinfos; - AppendRelInfo **right_appinfos; - int right_nappinfos; - - memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo)); - left_appinfos = find_appinfos_by_relids(root, left_relids, - &left_nappinfos); - right_appinfos = find_appinfos_by_relids(root, right_relids, - &right_nappinfos); - - sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand, - left_nappinfos, left_appinfos); - sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand, - right_nappinfos, - right_appinfos); - sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand, - left_nappinfos, left_appinfos); - sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand, - right_nappinfos, - right_appinfos); - sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root, - (Node *) sjinfo->semi_rhs_exprs, - right_nappinfos, - right_appinfos); - - pfree(left_appinfos); - pfree(right_appinfos); - - return sjinfo; -} - -/* - * find_appinfos_by_relids - * Find AppendRelInfo structures for all relations specified by relids. - * - * The AppendRelInfos are returned in an array, which can be pfree'd by the - * caller. *nappinfos is set to the number of entries in the array. - */ -AppendRelInfo ** -find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos) -{ - AppendRelInfo **appinfos; - int cnt = 0; - int i; - - *nappinfos = bms_num_members(relids); - appinfos = (AppendRelInfo **) palloc(sizeof(AppendRelInfo *) * *nappinfos); - - i = -1; - while ((i = bms_next_member(relids, i)) >= 0) - { - AppendRelInfo *appinfo = root->append_rel_array[i]; - - if (!appinfo) - elog(ERROR, "child rel %d not found in append_rel_array", i); - - appinfos[cnt++] = appinfo; - } - return appinfos; -} diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile index da9ccf32b4..8a5e2ad1bd 100644 --- a/src/backend/optimizer/util/Makefile +++ b/src/backend/optimizer/util/Makefile @@ -12,7 +12,8 @@ subdir = src/backend/optimizer/util top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = clauses.o joininfo.o orclauses.o pathnode.o placeholder.o \ - plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o +OBJS = appendinfo.o clauses.o inherit.o joininfo.o orclauses.o pathnode.o \ + placeholder.o plancat.o predtest.o relnode.o restrictinfo.o tlist.o \ + var.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c new file mode 100644 index 0000000000..9502535e53 --- /dev/null +++ b/src/backend/optimizer/util/appendinfo.c @@ -0,0 +1,851 @@ +/*------------------------------------------------------------------------- + * + * appendinfo.c + * Routines for mapping between append parent(s) and children + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/optimizer/path/appendinfo.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" +#include "parser/parsetree.h" +#include "utils/rel.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" + +typedef struct +{ + PlannerInfo *root; + int nappinfos; + AppendRelInfo **appinfos; +} adjust_appendrel_attrs_context; + +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); +static Node *adjust_appendrel_attrs_mutator(Node *node, + adjust_appendrel_attrs_context *context); +static Relids adjust_child_relids(Relids relids, int nappinfos, + AppendRelInfo **appinfos); +static List *adjust_inherited_tlist(List *tlist, + AppendRelInfo *context); + +AppendRelInfo * +make_append_rel_info(RelOptInfo *parentrel, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex) +{ + AppendRelInfo *appinfo = makeNode(AppendRelInfo); + + appinfo->parent_relid = parentrel->relid; + appinfo->child_relid = childRTindex; + appinfo->parent_reltype = parentrel->reltype; + appinfo->child_reltype = childreltype; + make_inh_translation_list(parentrel->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, + &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; + + return appinfo; +} + +/* + * adjust_appendrel_attrs + * Copy the specified query or expression and translate Vars referring to a + * parent rel to refer to the corresponding child rel instead. We also + * update rtindexes appearing outside Vars, such as resultRelation and + * jointree relids. + * + * Note: this is only applied after conversion of sublinks to subplans, + * so we don't need to cope with recursion into sub-queries. + * + * Note: this is not hugely different from what pullup_replace_vars() does; + * maybe we should try to fold the two routines together. + */ +Node * +adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, + AppendRelInfo **appinfos) +{ + Node *result; + adjust_appendrel_attrs_context context; + + context.root = root; + context.nappinfos = nappinfos; + context.appinfos = appinfos; + + /* If there's nothing to adjust, don't call this function. */ + Assert(nappinfos >= 1 && appinfos != NULL); + + /* + * Must be prepared to start with a Query or a bare expression tree. + */ + if (node && IsA(node, Query)) + { + Query *newnode; + int cnt; + + newnode = query_tree_mutator((Query *) node, + adjust_appendrel_attrs_mutator, + (void *) &context, + QTW_IGNORE_RC_SUBQUERIES); + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (newnode->resultRelation == appinfo->parent_relid) + { + newnode->resultRelation = appinfo->child_relid; + /* Fix tlist resnos too, if it's inherited UPDATE */ + if (newnode->commandType == CMD_UPDATE) + newnode->targetList = + adjust_inherited_tlist(newnode->targetList, + appinfo); + break; + } + } + + result = (Node *) newnode; + } + else + result = adjust_appendrel_attrs_mutator(node, &context); + + return result; +} + +static Node * +adjust_appendrel_attrs_mutator(Node *node, + adjust_appendrel_attrs_context *context) +{ + AppendRelInfo **appinfos = context->appinfos; + int nappinfos = context->nappinfos; + int cnt; + + if (node == NULL) + return NULL; + if (IsA(node, Var)) + { + Var *var = (Var *) copyObject(node); + AppendRelInfo *appinfo = NULL; + + for (cnt = 0; cnt < nappinfos; cnt++) + { + if (var->varno == appinfos[cnt]->parent_relid) + { + appinfo = appinfos[cnt]; + break; + } + } + + if (var->varlevelsup == 0 && appinfo) + { + var->varno = appinfo->child_relid; + var->varnoold = appinfo->child_relid; + if (var->varattno > 0) + { + Node *newnode; + + if (var->varattno > list_length(appinfo->translated_vars)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + var->varattno, get_rel_name(appinfo->parent_reloid)); + newnode = copyObject(list_nth(appinfo->translated_vars, + var->varattno - 1)); + if (newnode == NULL) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + var->varattno, get_rel_name(appinfo->parent_reloid)); + return newnode; + } + else if (var->varattno == 0) + { + /* + * Whole-row Var: if we are dealing with named rowtypes, we + * can use a whole-row Var for the child table plus a coercion + * step to convert the tuple layout to the parent's rowtype. + * Otherwise we have to generate a RowExpr. + */ + if (OidIsValid(appinfo->child_reltype)) + { + Assert(var->vartype == appinfo->parent_reltype); + if (appinfo->parent_reltype != appinfo->child_reltype) + { + ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr); + + r->arg = (Expr *) var; + r->resulttype = appinfo->parent_reltype; + r->convertformat = COERCE_IMPLICIT_CAST; + r->location = -1; + /* Make sure the Var node has the right type ID, too */ + var->vartype = appinfo->child_reltype; + return (Node *) r; + } + } + else + { + /* + * Build a RowExpr containing the translated variables. + * + * In practice var->vartype will always be RECORDOID here, + * so we need to come up with some suitable column names. + * We use the parent RTE's column names. + * + * Note: we can't get here for inheritance cases, so there + * is no need to worry that translated_vars might contain + * some dummy NULLs. + */ + RowExpr *rowexpr; + List *fields; + RangeTblEntry *rte; + + rte = rt_fetch(appinfo->parent_relid, + context->root->parse->rtable); + fields = copyObject(appinfo->translated_vars); + rowexpr = makeNode(RowExpr); + rowexpr->args = fields; + rowexpr->row_typeid = var->vartype; + rowexpr->row_format = COERCE_IMPLICIT_CAST; + rowexpr->colnames = copyObject(rte->eref->colnames); + rowexpr->location = -1; + + return (Node *) rowexpr; + } + } + /* system attributes don't need any other translation */ + } + return (Node *) var; + } + if (IsA(node, CurrentOfExpr)) + { + CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node); + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (cexpr->cvarno == appinfo->parent_relid) + { + cexpr->cvarno = appinfo->child_relid; + break; + } + } + return (Node *) cexpr; + } + if (IsA(node, RangeTblRef)) + { + RangeTblRef *rtr = (RangeTblRef *) copyObject(node); + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (rtr->rtindex == appinfo->parent_relid) + { + rtr->rtindex = appinfo->child_relid; + break; + } + } + return (Node *) rtr; + } + if (IsA(node, JoinExpr)) + { + /* Copy the JoinExpr node with correct mutation of subnodes */ + JoinExpr *j; + AppendRelInfo *appinfo; + + j = (JoinExpr *) expression_tree_mutator(node, + adjust_appendrel_attrs_mutator, + (void *) context); + /* now fix JoinExpr's rtindex (probably never happens) */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + appinfo = appinfos[cnt]; + + if (j->rtindex == appinfo->parent_relid) + { + j->rtindex = appinfo->child_relid; + break; + } + } + return (Node *) j; + } + if (IsA(node, PlaceHolderVar)) + { + /* Copy the PlaceHolderVar node with correct mutation of subnodes */ + PlaceHolderVar *phv; + + phv = (PlaceHolderVar *) expression_tree_mutator(node, + adjust_appendrel_attrs_mutator, + (void *) context); + /* now fix PlaceHolderVar's relid sets */ + if (phv->phlevelsup == 0) + phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos, + context->appinfos); + return (Node *) phv; + } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + + /* Shouldn't need to handle planner auxiliary nodes here */ + Assert(!IsA(node, AppendRelInfo)); + Assert(!IsA(node, PlaceHolderInfo)); + Assert(!IsA(node, MinMaxAggInfo)); + + /* + * We have to process RestrictInfo nodes specially. (Note: although + * set_append_rel_pathlist will hide RestrictInfos in the parent's + * baserestrictinfo list from us, it doesn't hide those in joininfo.) + */ + if (IsA(node, RestrictInfo)) + { + RestrictInfo *oldinfo = (RestrictInfo *) node; + RestrictInfo *newinfo = makeNode(RestrictInfo); + + /* Copy all flat-copiable fields */ + memcpy(newinfo, oldinfo, sizeof(RestrictInfo)); + + /* Recursively fix the clause itself */ + newinfo->clause = (Expr *) + adjust_appendrel_attrs_mutator((Node *) oldinfo->clause, context); + + /* and the modified version, if an OR clause */ + newinfo->orclause = (Expr *) + adjust_appendrel_attrs_mutator((Node *) oldinfo->orclause, context); + + /* adjust relid sets too */ + newinfo->clause_relids = adjust_child_relids(oldinfo->clause_relids, + context->nappinfos, + context->appinfos); + newinfo->required_relids = adjust_child_relids(oldinfo->required_relids, + context->nappinfos, + context->appinfos); + newinfo->outer_relids = adjust_child_relids(oldinfo->outer_relids, + context->nappinfos, + context->appinfos); + newinfo->nullable_relids = adjust_child_relids(oldinfo->nullable_relids, + context->nappinfos, + context->appinfos); + newinfo->left_relids = adjust_child_relids(oldinfo->left_relids, + context->nappinfos, + context->appinfos); + newinfo->right_relids = adjust_child_relids(oldinfo->right_relids, + context->nappinfos, + context->appinfos); + + /* + * Reset cached derivative fields, since these might need to have + * different values when considering the child relation. Note we + * don't reset left_ec/right_ec: each child variable is implicitly + * equivalent to its parent, so still a member of the same EC if any. + */ + newinfo->eval_cost.startup = -1; + newinfo->norm_selec = -1; + newinfo->outer_selec = -1; + newinfo->left_em = NULL; + newinfo->right_em = NULL; + newinfo->scansel_cache = NIL; + newinfo->left_bucketsize = -1; + newinfo->right_bucketsize = -1; + newinfo->left_mcvfreq = -1; + newinfo->right_mcvfreq = -1; + + return (Node *) newinfo; + } + + /* + * NOTE: we do not need to recurse into sublinks, because they should + * already have been converted to subplans before we see them. + */ + Assert(!IsA(node, SubLink)); + Assert(!IsA(node, Query)); + + return expression_tree_mutator(node, adjust_appendrel_attrs_mutator, + (void *) context); +} + +/* + * Substitute child relids for parent relids in a Relid set. The array of + * appinfos specifies the substitutions to be performed. + */ +static Relids +adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos) +{ + Bitmapset *result = NULL; + int cnt; + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + /* Remove parent, add child */ + if (bms_is_member(appinfo->parent_relid, relids)) + { + /* Make a copy if we are changing the set. */ + if (!result) + result = bms_copy(relids); + + result = bms_del_member(result, appinfo->parent_relid); + result = bms_add_member(result, appinfo->child_relid); + } + } + + /* If we made any changes, return the modified copy. */ + if (result) + return result; + + /* Otherwise, return the original set without modification. */ + return relids; +} + +/* + * Replace any relid present in top_parent_relids with its child in + * child_relids. Members of child_relids can be multiple levels below top + * parent in the partition hierarchy. + */ +Relids +adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, + Relids child_relids, Relids top_parent_relids) +{ + AppendRelInfo **appinfos; + int nappinfos; + Relids parent_relids = NULL; + Relids result; + Relids tmp_result = NULL; + int cnt; + + /* + * If the given relids set doesn't contain any of the top parent relids, + * it will remain unchanged. + */ + if (!bms_overlap(relids, top_parent_relids)) + return relids; + + appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); + + /* Construct relids set for the immediate parent of the given child. */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); + } + + /* Recurse if immediate parent is not the top parent. */ + if (!bms_equal(parent_relids, top_parent_relids)) + { + tmp_result = adjust_child_relids_multilevel(root, relids, + parent_relids, + top_parent_relids); + relids = tmp_result; + } + + result = adjust_child_relids(relids, nappinfos, appinfos); + + /* Free memory consumed by any intermediate result. */ + if (tmp_result) + bms_free(tmp_result); + bms_free(parent_relids); + pfree(appinfos); + + return result; +} + +/* + * adjust_appendrel_attrs_multilevel + * Apply Var translations from a toplevel appendrel parent down to a child. + * + * In some cases we need to translate expressions referencing a parent relation + * to reference an appendrel child that's multiple levels removed from it. + */ +Node * +adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, + Relids child_relids, + Relids top_parent_relids) +{ + AppendRelInfo **appinfos; + Bitmapset *parent_relids = NULL; + int nappinfos; + int cnt; + + Assert(bms_num_members(child_relids) == bms_num_members(top_parent_relids)); + + appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); + + /* Construct relids set for the immediate parent of given child. */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); + } + + /* Recurse if immediate parent is not the top parent. */ + if (!bms_equal(parent_relids, top_parent_relids)) + node = adjust_appendrel_attrs_multilevel(root, node, parent_relids, + top_parent_relids); + + /* Now translate for this child */ + node = adjust_appendrel_attrs(root, node, nappinfos, appinfos); + + pfree(appinfos); + + return node; +} + +/* + * Construct the SpecialJoinInfo for a child-join by translating + * SpecialJoinInfo for the join between parents. left_relids and right_relids + * are the relids of left and right side of the join respectively. + */ +SpecialJoinInfo * +build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, + Relids left_relids, Relids right_relids) +{ + SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo); + AppendRelInfo **left_appinfos; + int left_nappinfos; + AppendRelInfo **right_appinfos; + int right_nappinfos; + + memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo)); + left_appinfos = find_appinfos_by_relids(root, left_relids, + &left_nappinfos); + right_appinfos = find_appinfos_by_relids(root, right_relids, + &right_nappinfos); + + sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand, + left_nappinfos, left_appinfos); + sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand, + right_nappinfos, + right_appinfos); + sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand, + left_nappinfos, left_appinfos); + sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand, + right_nappinfos, + right_appinfos); + sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root, + (Node *) sjinfo->semi_rhs_exprs, + right_nappinfos, + right_appinfos); + + pfree(left_appinfos); + pfree(right_appinfos); + + return sjinfo; +} + +/* + * find_appinfos_by_relids + * Find AppendRelInfo structures for all relations specified by relids. + * + * The AppendRelInfos are returned in an array, which can be pfree'd by the + * caller. *nappinfos is set to the number of entries in the array. + */ +AppendRelInfo ** +find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos) +{ + AppendRelInfo **appinfos; + int cnt = 0; + int i; + + *nappinfos = bms_num_members(relids); + appinfos = (AppendRelInfo **) palloc(sizeof(AppendRelInfo *) * *nappinfos); + + i = -1; + while ((i = bms_next_member(relids, i)) >= 0) + { + AppendRelInfo *appinfo = root->append_rel_array[i]; + + if (!appinfo) + elog(ERROR, "child rel %d not found in append_rel_array", i); + + appinfos[cnt++] = appinfo; + } + return appinfos; +} + +/* + * make_inh_translation_list + * Build the list of translations from parent Vars to child Vars for + * an inheritance child. + * + * For paranoia's sake, we match type/collation as well as attribute name. + */ +static void +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) +{ + List *vars = NIL; + int oldnatts = old_tupdesc->natts; + int newnatts = new_tupdesc->natts; + int old_attno; + int new_attno = 0; + + for (old_attno = 0; old_attno < oldnatts; old_attno++) + { + Form_pg_attribute att; + char *attname; + Oid atttypid; + int32 atttypmod; + Oid attcollation; + + att = TupleDescAttr(old_tupdesc, old_attno); + if (att->attisdropped) + { + /* Just put NULL into this list entry */ + vars = lappend(vars, NULL); + continue; + } + attname = NameStr(att->attname); + atttypid = att->atttypid; + atttypmod = att->atttypmod; + attcollation = att->attcollation; + + /* + * When we are generating the "translation list" for the parent table + * of an inheritance set, no need to search for matches. + */ + if (from_rel == to_rel) + { + vars = lappend(vars, makeVar(newvarno, + (AttrNumber) (old_attno + 1), + atttypid, + atttypmod, + attcollation, + 0)); + continue; + } + + /* + * Otherwise we have to search for the matching column by name. + * There's no guarantee it'll have the same column position, because + * of cases like ALTER TABLE ADD COLUMN and multiple inheritance. + * However, in simple cases, the relative order of columns is mostly + * the same in both relations, so try the column of newrelation that + * follows immediately after the one that we just found, and if that + * fails, let syscache handle it. + */ + if (new_attno >= newnatts || + (att = TupleDescAttr(new_tupdesc, new_attno))->attisdropped || + strcmp(attname, NameStr(att->attname)) != 0) + { + HeapTuple newtup; + + newtup = SearchSysCacheAttName(to_rel, attname); + if (!newtup) + elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", + attname, get_rel_name(to_rel)); + new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; + ReleaseSysCache(newtup); + + att = TupleDescAttr(new_tupdesc, new_attno); + } + + /* Found it, check type and collation match */ + if (atttypid != att->atttypid || atttypmod != att->atttypmod) + elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", + attname, get_rel_name(to_rel)); + if (attcollation != att->attcollation) + elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", + attname, get_rel_name(to_rel)); + + vars = lappend(vars, makeVar(newvarno, + (AttrNumber) (new_attno + 1), + atttypid, + atttypmod, + attcollation, + 0)); + new_attno++; + } + + *translated_vars = vars; +} + +/* + * translate_col_privs + * Translate a bitmapset representing per-column privileges from the + * parent rel's attribute numbering to the child's. + * + * The only surprise here is that we don't translate a parent whole-row + * reference into a child whole-row reference. That would mean requiring + * permissions on all child columns, which is overly strict, since the + * query is really only going to reference the inherited columns. Instead + * we set the per-column bits for all inherited columns. + */ +Bitmapset * +translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars) +{ + Bitmapset *child_privs = NULL; + bool whole_row; + int attno; + ListCell *lc; + + /* System attributes have the same numbers in all tables */ + for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) + { + if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + attno - FirstLowInvalidHeapAttributeNumber); + } + + /* Check if parent has whole-row reference */ + whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, + parent_privs); + + /* And now translate the regular user attributes, using the vars list */ + attno = InvalidAttrNumber; + foreach(lc, translated_vars) + { + Var *var = lfirst_node(Var, lc); + + attno++; + if (var == NULL) /* ignore dropped columns */ + continue; + if (whole_row || + bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + var->varattno - FirstLowInvalidHeapAttributeNumber); + } + + return child_privs; +} + +/* + * Adjust the targetlist entries of an inherited UPDATE operation + * + * The expressions have already been fixed, but we have to make sure that + * the target resnos match the child table (they may not, in the case of + * a column that was added after-the-fact by ALTER TABLE). In some cases + * this can force us to re-order the tlist to preserve resno ordering. + * (We do all this work in special cases so that preptlist.c is fast for + * the typical case.) + * + * The given tlist has already been through expression_tree_mutator; + * therefore the TargetEntry nodes are fresh copies that it's okay to + * scribble on. + * + * Note that this is not needed for INSERT because INSERT isn't inheritable. + */ +static List * +adjust_inherited_tlist(List *tlist, AppendRelInfo *context) +{ + bool changed_it = false; + ListCell *tl; + List *new_tlist; + bool more; + int attrno; + + /* This should only happen for an inheritance case, not UNION ALL */ + Assert(OidIsValid(context->parent_reloid)); + + /* Scan tlist and update resnos to match attnums of child rel */ + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + Var *childvar; + + if (tle->resjunk) + continue; /* ignore junk items */ + + /* Look up the translation of this column: it must be a Var */ + if (tle->resno <= 0 || + tle->resno > list_length(context->translated_vars)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + tle->resno, get_rel_name(context->parent_reloid)); + childvar = (Var *) list_nth(context->translated_vars, tle->resno - 1); + if (childvar == NULL || !IsA(childvar, Var)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + tle->resno, get_rel_name(context->parent_reloid)); + + if (tle->resno != childvar->varattno) + { + tle->resno = childvar->varattno; + changed_it = true; + } + } + + /* + * If we changed anything, re-sort the tlist by resno, and make sure + * resjunk entries have resnos above the last real resno. The sort + * algorithm is a bit stupid, but for such a seldom-taken path, small is + * probably better than fast. + */ + if (!changed_it) + return tlist; + + new_tlist = NIL; + more = true; + for (attrno = 1; more; attrno++) + { + more = false; + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + + if (tle->resjunk) + continue; /* ignore junk items */ + + if (tle->resno == attrno) + new_tlist = lappend(new_tlist, tle); + else if (tle->resno > attrno) + more = true; + } + } + + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + + if (!tle->resjunk) + continue; /* here, ignore non-junk items */ + + tle->resno = attrno; + new_tlist = lappend(new_tlist, tle); + attrno++; + } + + return new_tlist; +} diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c new file mode 100644 index 0000000000..38a0a7635c --- /dev/null +++ b/src/backend/optimizer/util/inherit.c @@ -0,0 +1,776 @@ +/*------------------------------------------------------------------------- + * + * inherit.c + * Routines to process child relations in inheritance trees + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/optimizer/path/inherit.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <limits.h> + +#include "access/sysattr.h" +#include "catalog/partition.h" +#include "catalog/pg_class.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_type.h" +#include "nodes/makefuncs.h" +#include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" +#include "optimizer/inherit.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" +#include "optimizer/plancat.h" +#include "optimizer/planmain.h" +#include "optimizer/planner.h" +#include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" +#include "optimizer/tlist.h" +#include "optimizer/var.h" +#include "partitioning/partprune.h" +#include "utils/rel.h" + +static void expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti); +static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, + Index rti, RelOptInfo *rel); +static void expand_partitioned_rtentry(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p); +static RelOptInfo *build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); + + +/* + * expand_inherited_tables + * Expand each rangetable entry that represents an inheritance set + * into an "append relation". At the conclusion of this process, + * the "inh" flag is set in all and only those RTEs that are append + * relation parents. + */ +void +expand_inherited_tables(PlannerInfo *root) +{ + int orig_rtable_size; + Index rti; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; + + /* + * expand_append_rtentry may add RTEs to parse->rtable. The function is + * expected to recursively handle any RTEs that it creates with inh=true. + * So just scan as far as the original end of the rtable list. + */ + for (rti = 1; rti < orig_rtable_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; + + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + if (rte->inh) + expand_append_rtentry(root, brel, rte, rti); + } +} + +/* + * expand_append_rtentry + * This initializes RelOptInfos for an appendrel's child relations, if + * any + * + * 'rel' is the appendrel parent, whose range table entry ('rte') has been + * marked to require adding children. An appendrel parent could either + * be a subquery (if we flattened UNION ALL query) or a table that's known + * to have inheritance children. The latter consists of both regular + * inheritance parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as an appendrel child. The original RTE is + * considered to represent the whole inheritance set. + */ +static void +expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, + Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_append_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_inherited_rtentry(root, rte, rti, rel); + } +} + +/* + * expand_inherited_rtentry + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. + * + * Note that the original RTE is considered to represent the whole + * inheritance set. The first of the generated RTEs is an RTE for the same + * table, but with inh = false, to represent the parent table in its role + * as a simple member of the inheritance set. + * + * A childless table is never considered to be an inheritance set. For + * regular inheritance, a parent RTE must always have at least two associated + * AppendRelInfos: one corresponding to the parent table as a simple member of + * inheritance set and one or more corresponding to the actual children. + */ +static void +expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, + RelOptInfo *rel) +{ + Oid parentOID; + PlanRowMark *oldrc; + LOCKMODE lockmode = rte->rellockmode; + List *inhOIDs; + ListCell *l; + int num_children; + int num_children_added = 0; + + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); + parentOID = rte->relid; + + /* Scan for all members of inheritance set, acquire needed locks */ + inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); + + /* + * Check that there's at least one descendant, else treat as no-child + * case. This could happen despite has_subclass() check performed by + * subquery_planner, if table once had a child but no longer does. + */ + num_children = list_length(inhOIDs); + if (num_children < 2) + { + /* Clear flag before returning */ + rte->inh = false; + return; + } + + /* + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. + */ + oldrc = get_plan_rowmark(root->rowMarks, rti); + Assert(oldrc == NULL || oldrc->isParent); + + /* + * Must expand PlannerInfo arrays by num_children before we can add + * children. + */ + expand_planner_arrays(root, num_children); + + foreach(l, inhOIDs) + { + Oid childOID = lfirst_oid(l); + Relation newrelation; + RangeTblEntry *childrte; + Index childRTindex; + + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + + /* + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. + */ + if (RELATION_IS_OTHER_TEMP(newrelation)) + { + heap_close(newrelation, lockmode); + continue; + } + + (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation, &childrte, + &childRTindex); + Assert(childrte != NULL); + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + Assert(childRTindex > 0); + + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; + } + + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } +} + +/* + * expand_partitioned_rtentry + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * when this function is called for the root partitioned table. + */ +static void +expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel) +{ + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; + int i; + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; + + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); + + /* + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. + */ + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); + + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + expand_planner_arrays(root, bms_num_members(partindexes)); + + /* + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. + */ + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); + + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) + { + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + RangeTblEntry *childrte; + Index childRTindex; + + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); + + /* + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. + */ + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } + + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation, + &childrte, &childRTindex); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; + + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + + /* If the child is partitioned itself, expand it too. */ + if (childrel->part_scheme) + { + Assert(childrte->inh); + expand_partitioned_rtentry(root, childrte, childRTindex, + childrel); + } + } + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); + } +} + +/* + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. + * + * The return value is the RelOptInfo that's added. + * + * PlanRowMarks still carry the top-parent's RTI, and the top-parent's + * allMarkTypes field still accumulates values from all descendents. + * + * "parentrte" and "parentRTindex" are immediate parent's RTE and + * RTI. "top_parentrc" is top parent's PlanRowMark. + */ +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p) +{ + Query *parse = root->parse; + Oid childOID = RelationGetRelid(childrel); + RangeTblEntry *childrte; + Index childRTindex; + AppendRelInfo *appinfo; + RelOptInfo *childrelopt; + + /* + * Build an RTE for the child, and attach to query's rangetable list. We + * copy most fields of the parent's RTE, but replace relation OID and + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties + * individual children may have. (This is an intentional choice to make + * inherited RLS work like regular permissions checks.) The parent + * securityQuals will be propagated to children along with other base + * restriction clauses, so we don't need to do it here. + */ + childrte = copyObject(parentrte); + *childrte_p = childrte; + childrte->relid = childOID; + childrte->relkind = childrel->rd_rel->relkind; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); + childrte->requiredPerms = 0; + childrte->securityQuals = NIL; + parse->rtable = lappend(parse->rtable, childrte); + childRTindex = list_length(parse->rtable); + *childRTindex_p = childRTindex; + + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + childrel->rd_att, + childOID, + childrel->rd_rel->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); + + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); + } + + /* + * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE. + */ + if (top_parentrc) + { + PlanRowMark *childrc = makeNode(PlanRowMark); + + childrc->rti = childRTindex; + childrc->prti = top_parentrc->rti; + childrc->rowmarkId = top_parentrc->rowmarkId; + /* Reselect rowmark type, because relkind might not match parent */ + childrc->markType = select_rowmark_type(childrte, + top_parentrc->strength); + childrc->allMarkTypes = (1 << childrc->markType); + childrc->strength = top_parentrc->strength; + childrc->waitPolicy = top_parentrc->waitPolicy; + + /* + * We mark RowMarks for partitioned child tables as parent RowMarks so + * that the executor ignores them (except their existence means that + * the child tables be locked using appropriate mode). + */ + childrc->isParent = (childrte->relkind == RELKIND_PARTITIONED_TABLE); + + /* Include child's rowmark type in top parent's allMarkTypes */ + top_parentrc->allMarkTypes |= childrc->allMarkTypes; + + root->rowMarks = lappend(root->rowMarks, childrc); + } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need to create one, + * because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_append_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; +} + +/* + * build_append_child_rel + * Build a RelOptInfo for child relation of an append rel + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields based on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turn out to be self-contradictory, the + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + List *childquals; + ListCell *lc; + bool have_const_false_cq; + Index cq_min_security; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = false; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, parent->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual = (Node *) rinfo->clause; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, childqual, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might + * have securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals.) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except + * that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* + * not likely that we'd see constants here, so no + * check + */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, + false, + false, + security_level, + NULL, NULL, + NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* Set child's version of baserestrictinfo. */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; +} diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index d50d86b252..8ce88876c4 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -20,6 +20,7 @@ #include "foreign/fdwapi.h" #include "nodes/extensible.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index c23db9d78d..52a11f434f 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -17,6 +17,7 @@ #include <limits.h> #include "miscadmin.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index fcf8d6032c..e76906da1f 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -44,6 +44,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h new file mode 100644 index 0000000000..e205e78e6d --- /dev/null +++ b/src/include/optimizer/appendinfo.h @@ -0,0 +1,43 @@ +/*------------------------------------------------------------------------- + * + * appendinfo.h + * Routines for mapping expressions between append rel parent(s) and + * children + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/optimizer/appendinfo.h + * + *------------------------------------------------------------------------- + */ +#ifndef APPENDINFO_H +#define APPENDINFO_H + +#include "nodes/plannodes.h" +#include "nodes/relation.h" +#include "utils/relcache.h" + +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parentrel, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex); +extern Bitmapset *translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars); +extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, + int nappinfos, AppendRelInfo **appinfos); + +extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, + Relids child_relids, + Relids top_parent_relids); + +extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root, + Relids relids, int *nappinfos); + +extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, + SpecialJoinInfo *parent_sjinfo, + Relids left_relids, Relids right_relids); +extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, + Relids child_relids, Relids top_parent_relids); + +#endif /* APPENDINFO_H */ diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h new file mode 100644 index 0000000000..97043e3561 --- /dev/null +++ b/src/include/optimizer/inherit.h @@ -0,0 +1,25 @@ +/*------------------------------------------------------------------------- + * + * inherit.h + * prototypes for inherit.c. + * + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/optimizer/inherit.h + * + *------------------------------------------------------------------------- + */ +#ifndef INHERIT_H +#define INHERIT_H + +#include "nodes/relation.h" + +/* + * inherit.c + * utilities for dealing with append relations + */ +extern void expand_inherited_tables(PlannerInfo *root); + +#endif /* INHERIT_H */ diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index faae07d240..3ad9b4a77e 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -48,24 +48,5 @@ extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); */ extern RelOptInfo *plan_set_operations(PlannerInfo *root); -extern void expand_inherited_tables(PlannerInfo *root); -extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, - int nappinfos, AppendRelInfo **appinfos); -extern AppendRelInfo *make_append_rel_info(RelOptInfo *parentrel, - RangeTblEntry *parentrte, - TupleDesc childdesc, Oid childoid, Oid childreltype, - Index childRTindex); -extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, - Relids child_relids, - Relids top_parent_relids); - -extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root, - Relids relids, int *nappinfos); - -extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, - SpecialJoinInfo *parent_sjinfo, - Relids left_relids, Relids right_relids); -extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, - Relids child_relids, Relids top_parent_relids); #endif /* PREP_H */ -- 2.11.0
v10-0005-Teach-planner-to-only-process-unpruned-partition.patch (text/plain, 6.9K) download From a8222d2005bba6c588610340600e19ca47a0e78a Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 15 Oct 2018 10:59:24 +0900 Subject: [PATCH v10 5/6] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/inherit.c | 1 + src/backend/optimizer/util/relnode.c | 3 +++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/relation.h | 4 ++++ 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 6e321ec9e7..76531a05cf 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1424,6 +1424,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 85379a6c7d..431ed510a2 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6816,7 +6816,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -6824,9 +6826,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -6907,7 +6907,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -6919,7 +6918,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -6929,9 +6930,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 38a0a7635c..fd646c68f6 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -350,6 +350,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, /* Perform pruning. */ partindexes = prune_append_rel_partitions(parentrel); + parentrel->live_parts = partindexes; /* Must expand PlannerInfo arrays before we can add children. */ expand_planner_arrays(root, bms_num_members(partindexes)); diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 52a11f434f..6c7eafe6db 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1748,6 +1748,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index e76906da1f..8bc2f5c5c2 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -441,29 +441,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 458be2769e..c73e7017f4 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -730,6 +730,10 @@ typedef struct RelOptInfo Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Indexes into part_rels of the non-NULL + * RelOptInfos of unpruned partitions; exists + * to avoid having to iterate over the entire + * part_rels array to filter NULL entries. */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.11.0
v10-0006-Do-not-lock-all-partitions-at-the-beginning.patch (text/plain, 1.8K) download From 790f3adda9b39f84799b701ca36b8060c6b5ecba Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 12 Oct 2018 19:12:53 +0900 Subject: [PATCH v10 6/6] Do not lock all partitions at the beginning --- src/backend/optimizer/util/inherit.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index fd646c68f6..9e104d809a 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -317,10 +317,6 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, * * Partitions are added to the query in order in which they are found in * the parent's PartitionDesc. - * - * Note: even though only the unpruned partitions will be added to the - * resulting plan, this still locks *all* partitions via find_all_inheritors - * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, @@ -334,10 +330,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, parentrel->inh_root_parent : parentRTindex; - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - /* * Initialize partitioned_child_rels to contain this RT index. * @@ -373,8 +365,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, RangeTblEntry *childrte; Index childRTindex; - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); + newrelation = heap_open(childOID, lockmode); Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-03 15:39  Justin Pryzby <pryzby@telsasoft.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 1 reply; 265+ messages in thread

From: Justin Pryzby @ 2019-01-03 15:39 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Hi,

I don't think I can't help with code review, but I loaded our largest
customer's schema into pg12dev and tested with this patch.  It's working well -
thanks for your work.

Details follow.

We have tooo many tables+indices so this vastly improves planning speed.  Our
large customers have ~300 parent tables and total ~20k child tables with total
~80k indices.  Our largest tables heirarchies have ~1200 child tables, which
may have as many as 6-8 indices each.  And 5-10 of the largest heirarchies are
unioned together in a view.

Running pg11.1, explaining query for the largest view with condition eliminates
all but today's tables can take several minutes with a cold cache, due to not
only stat()ing every file in every table in a partition heirarchy, before
pruning, but also actually open()ing all their indices.

Running 11dev with your v10 patch applied, this takes 2244ms with empty buffer
cache after postmaster restarted on a totally untuned instance (and a new
backend, with no cached opened files).

I was curious why it took even 2sec, and why it did so many opens() (but not
20k of them that PG11 does):

[pryzbyj@database postgresql]$ cut -d'(' -f1 /tmp/strace-12dev-explain |sort |uniq -c |sort -nr
   2544 lseek
   1263 open
   ...

It turns out 1050 open()s are due to historic data which is no longer being
loaded and therefor never converted to relkind=p (but hasn't exceeded the
retention interval so not yet DROPped, either).

Cheers,
Justin




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-07 08:40  David Rowley <david.rowley@2ndquadrant.com>
  parent: Justin Pryzby <pryzby@telsasoft.com>
  0 siblings, 2 replies; 265+ messages in thread

From: David Rowley @ 2019-01-07 08:40 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On Fri, 4 Jan 2019 at 04:39, Justin Pryzby <pryzby@telsasoft.com> wrote:
> Running 11dev with your v10 patch applied, this takes 2244ms with empty buffer
> cache after postmaster restarted on a totally untuned instance (and a new
> backend, with no cached opened files).
>
> I was curious why it took even 2sec, and why it did so many opens() (but not
> 20k of them that PG11 does):

It would be pretty hard to know that without seeing the query plan.

-- 
 David Rowley                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-07 09:17  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: David Rowley <david.rowley@2ndquadrant.com>
  1 sibling, 0 replies; 265+ messages in thread

From: Amit Langote @ 2019-01-07 09:17 UTC (permalink / raw)
  To: David Rowley <david.rowley@2ndquadrant.com>; Justin Pryzby <pryzby@telsasoft.com>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Thanks Justin for reporting the results of your testing.

On 2019/01/07 17:40, David Rowley wrote:
> On Fri, 4 Jan 2019 at 04:39, Justin Pryzby <pryzby@telsasoft.com> wrote:
>> Running 11dev with your v10 patch applied, this takes 2244ms with empty buffer
>> cache after postmaster restarted on a totally untuned instance (and a new
>> backend, with no cached opened files).
>>
>> I was curious why it took even 2sec, and why it did so many opens() (but not
>> 20k of them that PG11 does):
> 
> It would be pretty hard to know that without seeing the query plan.

Yeah, I too would be curious to see if the resulting plan really needs to
do those open()s.  If all the open()s being seen here are accounted for by
un-pruned partitions (across the UNION ALL) contained in the plan and
their indexes, then the patch has done enough to help.  If the open()s can
be traced to pruned partitions, then there's something wrong with the patch.

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-07 14:13  Justin Pryzby <pryzby@telsasoft.com>
  parent: David Rowley <david.rowley@2ndquadrant.com>
  1 sibling, 1 reply; 265+ messages in thread

From: Justin Pryzby @ 2019-01-07 14:13 UTC (permalink / raw)
  To: David Rowley <david.rowley@2ndquadrant.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On Mon, Jan 07, 2019 at 09:40:50PM +1300, David Rowley wrote:
> On Fri, 4 Jan 2019 at 04:39, Justin Pryzby <pryzby@telsasoft.com> wrote:
> > Running 11dev with your v10 patch applied, this takes 2244ms with empty buffer
> > cache after postmaster restarted on a totally untuned instance (and a new
> > backend, with no cached opened files).
> >
> > I was curious why it took even 2sec, and why it did so many opens() (but not
> > 20k of them that PG11 does):
> 
> It would be pretty hard to know that without seeing the query plan.

The issue was this:
> > It turns out 1050 open()s are due to historic data which is no longer being
> > loaded and therefor never converted to relkind=p (but hasn't exceeded the
> > retention interval so not yet DROPped, either).

So there's no evidence of any issue with the patch.

Justin




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-08 01:34  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Justin Pryzby <pryzby@telsasoft.com>
  0 siblings, 0 replies; 265+ messages in thread

From: Amit Langote @ 2019-01-08 01:34 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; David Rowley <david.rowley@2ndquadrant.com>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On 2019/01/07 23:13, Justin Pryzby wrote:
> The issue was this:
>>> It turns out 1050 open()s are due to historic data which is no longer being
>>> loaded and therefor never converted to relkind=p (but hasn't exceeded the
>>> retention interval so not yet DROPped, either).
> 
> So there's no evidence of any issue with the patch.

Ah, so by this you had meant that the historic data is still a old-style
(9.6-style) inheritance hierarchy, which gets folded under the UNION ALL
whose other children are new-style partitioned tables.

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-08 06:30  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 3 replies; 265+ messages in thread

From: Amit Langote @ 2019-01-08 06:30 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: 'David Rowley' <david.rowley@2ndquadrant.com>; 'Alvaro Herrera' <alvherre@2ndquadrant.com>; pgsql-hackers

On 2018/12/27 20:25, Amit Langote wrote:
> Here's the v10 of the patch.  I didn't get chance to do more changes than
> those described above and address Imai-san's review comments yesterday.
> 
> Have a wonderful new year!  I'll be back on January 7 to reply on this thread.

Rebased due to changed copyright year in prepunion.c.  Also realized that
the new files added by patch 0004 still had 2018 in them.

Thanks,
Amit

Attachments: download all (6 files, .tar.gz)

v11-0001-Overhaul-inheritance-update-delete-planning.patch (text/plain, 71.2K) download From 282383be9a015584e27de1c1ddc3c6bec747341e Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 11:18:12 +0900 Subject: [PATCH v11 1/6] Overhaul inheritance update/delete planning With the current method, inheritance_planner, which handles the planning for update/delete commands targeting inheritance trees, performs the *whole* planning for each child table. That involves translating the query tree to set a given child table in place of the original inheritance root table as the query's target table, followed by calling grouping_planner on the translated query, which performs query planning and additional steps needed to apply correct target list based on the child target table. That's inefficient for two reasons: 1. partprune.c cannot be used to perform partition pruning, because it can only be invoked from query_planner. With the current approach, query_planner only sees the individual partitions in the query tree it receives, not the parent partitioned table. This leaves each partition to be pruned using constraint exclusion. (constraint exclusion cannon prune hash partitions which means UPDATE/DELETE don't support hash partition pruning.) 2. Repeated invocation of query_planner results in allocating large amounts of memory, especially if there are many child tables. Also, it involves repeatedly performing the same processing, such as jointree processing. This commit addresses both of the issues by rewriting portions of inheritance_planner and adding some supporting functionality to allpath.c. With the new implementation, inheritance_planner calls query_planner only once at the beginning with the original unmodified query, which creates the access paths of individual relations after pruning any unnecessary partitions. For each target child relation, it also creates a sub-PartitionInfo containing translated version of the query and a targetlist suitable for the child. Child PlannerInfos are saved in the top PlannerInfo for using later. If the query involves join against the target relation, join paths are created for each target child relation by replacing the original target table in the join tree by a given child table. Join relations (RelOptInfos thereof) for all target child relations are collected in a global list in the top PlannerInfo. After creating the join paths for all target child relations, inheritance_planner calls grouping_planner() on each child join relation using the previously created child PlannerInfo to finish up the paths such that they produce query's top-level target list expanded according to a given child relation's descriptor. grouping_planner()'s interface is modified so that we can pass it what's called a 'planned_rel', a RelOptInfo that already contains the necessary paths needed to produce its output. This removes some existing code in inheritance_planner that dealt with any subquery RTEs in the query. The rationale of that code was that the subquery RTEs may change during each iteration of planning (that is, for different children), so different iterations better use different copies of those RTEs. That was handled by making fresh copies of those RTEs for each iteration of planning which were appended to the range table, accompanied by modifying all expressions and auxiliary structures that referenced the original subquery RTEs to instead reference the copies (that is, change the varnos). This copying meant we would end up adding S * N new entries to the original range table by the time we got to the last unpruned child, where S is the number of subquery RTEs in the original query and N the number of unpruned children. Since with the new code we perform planning just once, I think we don't need this special handling. Actually, there is a regression test output change due to no longer having copies of subquery RTEs (see the diff of partition_join.out file.) Also we no longer need the hack in relation_excluded_by_constraints() involving setting PlannerInfo.inhTargetKind to enable constraint exclusion for target child relation, which is no longer needed. Constraint exclusion now runs during query_planner step described above. Regression test output change in partition_join.out is due to the fact that we no longer create duplicates of subquery RTEs in child range tables. --- doc/src/sgml/ddl.sgml | 14 +- src/backend/nodes/outfuncs.c | 1 - src/backend/optimizer/path/allpaths.c | 602 +++++++++++++++++++++++++-- src/backend/optimizer/path/equivclass.c | 27 +- src/backend/optimizer/plan/createplan.c | 10 - src/backend/optimizer/plan/planmain.c | 166 +++++++- src/backend/optimizer/plan/planner.c | 434 +++++-------------- src/backend/optimizer/prep/prepjointree.c | 1 - src/backend/optimizer/prep/prepunion.c | 33 +- src/backend/optimizer/util/plancat.c | 50 +-- src/include/nodes/relation.h | 34 +- src/include/optimizer/paths.h | 3 +- src/test/regress/expected/partition_join.out | 4 +- 13 files changed, 937 insertions(+), 442 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 85e4358988..0333f673b3 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -4439,15 +4439,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <note> <para> - Currently, pruning of partitions during the planning of an - <command>UPDATE</command> or <command>DELETE</command> command is - implemented using the constraint exclusion method (however, it is - controlled by the <literal>enable_partition_pruning</literal> rather than - <literal>constraint_exclusion</literal>) &mdash; see the following section - for details and caveats that apply. - </para> - - <para> Execution-time partition pruning currently only occurs for the <literal>Append</literal> and <literal>MergeAppend</literal> node types. It is not yet implemented for the <literal>ModifyTable</literal> node @@ -4470,9 +4461,8 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <para> <firstterm>Constraint exclusion</firstterm> is a query optimization - technique similar to partition pruning. While it is primarily used - for partitioning implemented using the legacy inheritance method, it can be - used for other purposes, including with declarative partitioning. + technique similar to partition pruning. It is primarily used + for partitioning implemented using the legacy inheritance method. </para> <para> diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 0fde876c77..f6112e1b4c 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2210,7 +2210,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); WRITE_UINT_FIELD(qual_security_level); - WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind); WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasDeletedRTEs); diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 256fe16cdb..1e1dfaac47 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -36,6 +36,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" @@ -91,8 +92,12 @@ static void set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); +static void set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte); static void set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); +static void set_inherit_target_rel_pathlists(PlannerInfo *root, + RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, @@ -119,6 +124,8 @@ static void set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); +static void inheritance_make_rel_from_joinlist(PlannerInfo *root, + List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, pushdown_safety_info *safetyInfo); static bool recurse_pushdown_safe(Node *setOp, Query *topquery, @@ -149,27 +156,6 @@ make_one_rel(PlannerInfo *root, List *joinlist) Index rti; double total_pages; - /* - * Construct the all_baserels Relids set. - */ - root->all_baserels = NULL; - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - - /* there may be empty slots corresponding to non-baserel RTEs */ - if (brel == NULL) - continue; - - Assert(brel->relid == rti); /* sanity check on array */ - - /* ignore RTEs that are "other rels" */ - if (brel->reloptkind != RELOPT_BASEREL) - continue; - - root->all_baserels = bms_add_member(root->all_baserels, brel->relid); - } - /* Mark base rels as to whether we care about fast-start plans */ set_base_rel_consider_startup(root); @@ -217,13 +203,34 @@ make_one_rel(PlannerInfo *root, List *joinlist) /* * Generate access paths for the entire join tree. + * + * For UPDATE/DELETE on an inheritance parent, join paths should be + * generated for each child result rel separately. */ - rel = make_rel_from_joinlist(root, joinlist); + if (root->parse->resultRelation && + root->simple_rte_array[root->parse->resultRelation]->inh) + { + inheritance_make_rel_from_joinlist(root, joinlist); - /* - * The result should join all and only the query's base rels. - */ - Assert(bms_equal(rel->relids, root->all_baserels)); + /* + * Return the RelOptInfo of original target relation, although this + * doesn't really contain the final path. inheritance_planner + * from where we got here will generate the final path, but it will + * do so by iterative over child subroots, not through this + * RelOptInfo. + */ + rel = find_base_rel(root, root->parse->resultRelation); + } + else + { + rel = make_rel_from_joinlist(root, joinlist); + + /* + * The result should join all and only the query's base rels. + */ + Assert(bms_equal(rel->relids, root->all_baserels)); + + } return rel; } @@ -373,8 +380,15 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_size(root, rel, rti, rte); + /* + * If it's a target relation, set the sizes of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (rti == root->parse->resultRelation) + set_inherit_target_rel_sizes(root, rel, rti, rte); + else + set_append_rel_size(root, rel, rti, rte); } else { @@ -458,14 +472,26 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { + bool inherited_update = false; + if (IS_DUMMY_REL(rel)) { /* We already proved the relation empty, so nothing more to do */ } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_pathlist(root, rel, rti, rte); + /* + * If it's a target relation, set the pathlists of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (rti == root->parse->resultRelation) + { + inherited_update = true; + set_inherit_target_rel_pathlists(root, rel, rti, rte); + } + else + set_append_rel_pathlist(root, rel, rti, rte); } else { @@ -540,8 +566,12 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, if (set_rel_pathlist_hook) (*set_rel_pathlist_hook) (root, rel, rti, rte); - /* Now find the cheapest of the paths for this rel */ - set_cheapest(rel); + /* + * Now find the cheapest of the paths for this rel, unless it's an + * inheritance parent and this is an update/delete operation. + */ + if (!inherited_update) + set_cheapest(rel); #ifdef OPTIMIZER_DEBUG debug_print_rel(root, rel); @@ -903,6 +933,321 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) } /* + * set_inherit_target_rel_sizes + * Set size estimates for the child target relations + * + * The passed-in rel represents the target relation of the query that is + * known to have inheritance children. This is very much like + * set_append_rel_size, except it doesn't set the size estimates for the + * passed-in rel itself, because we don't need to "append" the children + * in this case. + */ +static void +set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + ListCell *l; + bool has_live_children; + Relids live_children = NULL; + bool did_pruning = false; + + /* Guard against stack overflow due to overly deep inheritance tree. */ + check_stack_depth(); + + Assert(IS_SIMPLE_REL(rel)); + + /* + * If the partitioned relation has any baserestrictinfo quals then we + * attempt to use these quals to prune away partitions that cannot + * possibly contain any tuples matching these quals. In this case we'll + * store the relids of all partitions which could possibly contain a + * matching tuple, and skip anything else in the loop below. + */ + if (enable_partition_pruning && + rte->relkind == RELKIND_PARTITIONED_TABLE && + rel->baserestrictinfo != NIL) + { + live_children = prune_append_rel_partitions(rel); + did_pruning = true; + } + + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + List *childquals; + Index cq_min_security; + bool have_const_false_cq; + PlannerInfo *subroot; + ListCell *lc; + List *translated_exprs, + *child_target_exprs; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + subroot = root->inh_target_child_roots[childRTindex]; + + /* + * The child rel's RelOptInfo was already created during + * add_base_rels_to_query. + */ + childrel = find_base_rel(root, childRTindex); + Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = NIL; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, + (Node *) rinfo->clause, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + /* track minimum security level among child quals */ + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might have + * securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals, see expand_inherited_rtentry().) Pull any such + * securityQuals up into the baserestrictinfo for the child. This is + * similar to process_security_barrier_quals() for the parent rel, + * except that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* not likely that we'd see constants here, so no check */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, false, false, + security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* + * OK, we've got all the baserestrictinfo quals for this child. + */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) + { + /* This partition was pruned; skip it. */ + set_dummy_rel_pathlist(childrel); + continue; + } + + if (relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* + * This child need not be scanned, so we can omit it from the + * appendrel. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Add missing Vars to child's reltarget. + * + * add_inherit_target_child_root() would've added only those that are + * needed to be present in the top-level tlist (or ones that + * preprocess_targetlist thinks are needed to be in the tlist.) We + * may need other attributes such as those contained in WHERE clauses, + * which are already computed for the parent during + * deconstruct_jointree processing of the original query (child's + * query never goes through deconstruct_jointree.) + */ + translated_exprs = (List *) + adjust_appendrel_attrs(root, + (Node *) rel->reltarget->exprs, + 1, &appinfo); + child_target_exprs = childrel->reltarget->exprs; + foreach(lc, translated_exprs) + { + Expr *expr = lfirst(lc); + + if (!list_member(child_target_exprs, expr)) + child_target_exprs = lappend(child_target_exprs, expr); + } + + /* + * We have to make child entries in the EquivalenceClass data + * structures as well. This is needed either if the parent + * participates in some eclass joins (because we will want to consider + * inner-indexscan joins on the individual children) or if the parent + * has useful pathkeys (because we should try to build MergeAppend + * paths that produce those sort orderings). Even if this child is + * deemed dummy, it may fall on nullable side in a child-join, which + * in turn may participate in a MergeAppend, where we will need the + * EquivalenceClass data structures. + */ + if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) + add_child_rel_equivalences(subroot, appinfo, rel, childrel, true); + childrel->has_eclass_joins = rel->has_eclass_joins; + + /* CE failed, so finish copying/modifying join quals. */ + childrel->joininfo = (List *) + adjust_appendrel_attrs(subroot, + (Node *) rel->joininfo, + 1, &appinfo); + + /* + * If parallelism is allowable for this query in general, see whether + * it's allowable for this childrel in particular. But if we've + * already decided the appendrel is not parallel-safe as a whole, + * there's no point in considering parallelism for this child. For + * consistency, do this before calling set_rel_size() for the child. + */ + if (root->glob->parallelModeOK && rel->consider_parallel) + set_rel_consider_parallel(subroot, childrel, childRTE); + + /* + * Compute the child's size using possibly modified subroot. + */ + set_rel_size(subroot, childrel, childRTindex, childRTE); + + /* + * It is possible that constraint exclusion detected a contradiction + * within a child subquery, even though we didn't prove one above. If + * so, we can skip this child. + */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* + * If any live child is not parallel-safe, treat the whole appendrel + * as not parallel-safe. In future we might be able to generate plans + * in which some children are farmed out to workers while others are + * not; but we don't have that today, so it's a waste to consider + * partial paths anywhere in the appendrel unless it's all safe. + * (Child rels visited before this one will be unmarked in + * set_append_rel_pathlist().) + */ + if (!childrel->consider_parallel) + rel->consider_parallel = false; + + /* + * Accumulate size information from each live child. + */ + Assert(childrel->rows > 0); + + /* We have at least one live child. */ + has_live_children = true; + } + + if (!has_live_children) + { + /* + * All children were excluded by constraints, so mark the relation + * ass dummy. We must do this in this phase so that the rel's + * dummy-ness is visible when we generate paths for other rels. + */ + set_dummy_rel_pathlist(rel); + } + else + /* + * Set a non-zero value here to cope with the caller's requirement + * that non-dummy relations are actually not empty. We don't try to + * be accurate here, because we're not going to create a path that + * combines the children outputs. + */ + rel->rows = 1; +} + +/* * set_append_rel_size * Set size estimates for a simple "append relation" * @@ -1045,7 +1390,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, * EquivalenceClass data structures. */ if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) - add_child_rel_equivalences(root, appinfo, rel, childrel); + add_child_rel_equivalences(root, appinfo, rel, childrel, + false); childrel->has_eclass_joins = rel->has_eclass_joins; /* @@ -1327,6 +1673,63 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, } /* + * set_inherit_target_rel_pathlists + * Build access paths for the child target relations + * + * Similar to set_append_rel_pathlist, except that we build paths of the + * children, but don't build an Append path. + */ +static void +set_inherit_target_rel_pathlists(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + ListCell *l; + + /* Nothing to do if all the children were excluded. */ + if (IS_DUMMY_REL(rel)) + return; + + /* Generate access paths for each of the children of passed-in rel */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + /* Re-locate the child RTE and RelOptInfo */ + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + childrel = root->simple_rel_array[childRTindex]; + + /* + * If set_append_rel_size() decided the parent appendrel was + * parallel-unsafe at some point after visiting this child rel, we + * need to propagate the unsafety marking down to the child, so that + * we don't generate useless partial paths for it. + */ + if (!rel->consider_parallel) + childrel->consider_parallel = false; + + /* + * Compute the child's access paths. + */ + set_rel_pathlist(root, childrel, childRTindex, childRTE); + + /* + * If child is dummy, ignore it. + */ + if (IS_DUMMY_REL(childrel)) + continue; + } +} + +/* * set_append_rel_pathlist * Build access paths for an "append relation" */ @@ -2624,6 +3027,139 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows) } /* + * inheritance_make_rel_from_joinlist + * Perform join planning for all non-dummy leaf inheritance children + * in their role as an UPDATE/DELETE query's target relation + * + * If a child relation is a partitioned table, its children are processed in + * turn by recursively calling this function. + */ +static void +inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; +#ifdef USE_ASSERT_CHECKING + Relids all_baserels; +#endif + + /* + * For UPDATE/DELETE queries, the top parent can only ever be a table. + * As a contrast, it could be a UNION ALL subquery in the case of SELECT. + */ + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + Assert(planner_rt_fetch(resultRelation, root)->rtekind == RTE_RELATION); + + /* Nothing to do. */ + if (IS_DUMMY_REL(find_base_rel(root, resultRelation))) + return; + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + PlannerInfo *subroot; + RelOptInfo *childrel; + List *translated_joinlist; + + if (appinfo->parent_relid != resultRelation) + continue; + + subroot = root->inh_target_child_roots[appinfo->child_relid]; + Assert(subroot->parse->resultRelation > 0); + childrel = find_base_rel(root, appinfo->child_relid); + + /* Ignore excluded/pruned children. */ + if (IS_DUMMY_REL(childrel)) + continue; + + root->inh_target_child_rels = lappend_int(root->inh_target_child_rels, + appinfo->child_relid); + + /* + * Modify joinlist such that relations joined to the top parent rel + * appear to be joined to the child rel instead. Do the same for + * any SpecialJoinInfo structs. + */ + translated_joinlist = (List *) + adjust_appendrel_attrs(subroot, + (Node *) joinlist, + 1, &appinfo); + subroot->join_info_list = (List *) + adjust_appendrel_attrs(subroot, + (Node *) root->join_info_list, + 1, &appinfo); + + /* + * Sub-partitioned tables have to be processed recursively using the + * translated subroot as the parent, because AppendRelInfos link + * sub-partitions to their immediate parents, not the root partitioned + * table. + */ + if (childrel->part_scheme != NULL) + { + inheritance_make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * Add this child relation as a placeholder in the parent root's + * inh_target_child_joinrels so that inheritance_planner see same + * number of entries as inh_target_child_rels. + */ + root->inh_target_child_joinrels = + lappend(root->inh_target_child_joinrels, childrel); + + /* Also propagate this child's own children into parent's list. */ + if (subroot->inh_target_child_rels != NIL) + { + root->inh_target_child_rels = + list_concat(root->inh_target_child_rels, + subroot->inh_target_child_rels); + root->inh_target_child_joinrels = + list_concat(root->inh_target_child_joinrels, + subroot->inh_target_child_joinrels); + } + continue; + } + + /* + * Since we added the child rel directly into the join tree, we must + * modify it to be a "base" rel instead of an "other" rel, which the + * join planning code expects the relations being joined to be. + */ + childrel->reloptkind = RELOPT_BASEREL; + + Assert(subroot->join_rel_list == NIL); + Assert(subroot->join_rel_hash == NULL); + + /* Perform join planning and save the resulting RelOptInfo. */ + childrel = make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * Remember this child target rel. inheritance_planner will perform + * the remaining steps of planning for each child relation separately. + * Specifically, it will call grouping_planner on every + * RelOptInfo contained in the inh_target_child_rels list, each of + * which represents the source of tuples to be modified for a given + * target child rel. + */ + root->inh_target_child_joinrels = + lappend(root->inh_target_child_joinrels, childrel); +#ifdef USE_ASSERT_CHECKING + /* + * The following implements essentially the same Assert as in + * make_one_rel, our caller. + */ + all_baserels = bms_copy(root->all_baserels); + all_baserels = bms_del_member(all_baserels, + root->parse->resultRelation); + all_baserels = bms_add_member(all_baserels, + subroot->parse->resultRelation); + Assert(bms_equal(childrel->relids, all_baserels)); +#endif + } +} + +/* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. * diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 0d994bd9ae..255e8f86a0 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -2104,12 +2104,18 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root, * * parent_rel and child_rel could be derived from appinfo, but since the * caller has already computed them, we might as well just pass them in. + * + * If 'child_is_target' is true then the child EC members *replace* the + * corresponding parent members. In that case, 'root' is the child target + * relation's dedicated PlannerInfo so it makes sense to remove the parent + * ECs altogether, because they're of no use. */ void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, - RelOptInfo *child_rel) + RelOptInfo *child_rel, + bool child_is_target) { ListCell *lc1; @@ -2117,6 +2123,8 @@ add_child_rel_equivalences(PlannerInfo *root, { EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc1); ListCell *lc2; + ListCell *prev; + ListCell *next; /* * If this EC contains a volatile expression, then generating child @@ -2134,12 +2142,18 @@ add_child_rel_equivalences(PlannerInfo *root, !bms_is_subset(parent_rel->relids, cur_ec->ec_relids)) continue; - foreach(lc2, cur_ec->ec_members) + prev = NULL; + for (lc2 = list_head(cur_ec->ec_members); lc2 != NULL; lc2 = next) { EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2); + next = lnext(lc2); + if (cur_em->em_is_const) + { + prev = lc2; continue; /* ignore consts here */ + } /* Does it reference parent_rel? */ if (bms_overlap(cur_em->em_relids, parent_rel->relids)) @@ -2177,10 +2191,17 @@ add_child_rel_equivalences(PlannerInfo *root, child_rel->relids); } + /* Delete the parent EC member. */ + if (child_is_target) + cur_ec->ec_members = list_delete_cell(cur_ec->ec_members, + lc2, prev); + (void) add_eq_member(cur_ec, child_expr, new_relids, new_nullable_relids, - true, cur_em->em_datatype); + !child_is_target, cur_em->em_datatype); } + else + prev = lc2; } } } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 066685c3c7..8f1769b5c6 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -2022,12 +2022,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) /* * During setrefs.c, we'll need the grouping_map to fix up the cols lists * in GroupingFunc nodes. Save it for setrefs.c to use. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be grouping in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->grouping_map == NULL); root->grouping_map = grouping_map; @@ -2184,12 +2179,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) * with InitPlan output params. (We can't just do that locally in the * MinMaxAgg node, because path nodes above here may have Agg references * as well.) Save the mmaggregates list to tell setrefs.c to do that. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be aggregates in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->minmax_aggs == NIL); root->minmax_aggs = best_path->mmaggregates; diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 0e0a543708..04238ef047 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -26,7 +26,11 @@ #include "optimizer/paths.h" #include "optimizer/placeholder.h" #include "optimizer/planmain.h" +#include "optimizer/prep.h" +static void add_inherited_target_child_roots(PlannerInfo *root); +static PlannerInfo *adjust_inherited_target_child_root(PlannerInfo *root, + AppendRelInfo *appinfo); /* * query_planner @@ -57,6 +61,8 @@ query_planner(PlannerInfo *root, List *tlist, Query *parse = root->parse; List *joinlist; RelOptInfo *final_rel; + bool inherited_update; + Index rti; /* * If the query has an empty join tree, then it's something easy like @@ -230,14 +236,168 @@ query_planner(PlannerInfo *root, List *tlist, extract_restriction_or_clauses(root); /* + * Construct the all_baserels Relids set. + */ + root->all_baserels = NULL; + for (rti = 1; rti < root->simple_rel_array_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + Assert(brel->relid == rti); /* sanity check on array */ + + /* ignore RTEs that are "other rels" */ + if (brel->reloptkind != RELOPT_BASEREL) + continue; + + root->all_baserels = bms_add_member(root->all_baserels, brel->relid); + } + + inherited_update = (parse->resultRelation && + root->simple_rte_array[parse->resultRelation]->inh); + + /* + * Add child subroots needed to use during planning for individual child + * targets + */ + if (inherited_update) + { + root->inh_target_child_roots = (PlannerInfo **) + palloc0(root->simple_rel_array_size * + sizeof(PlannerInfo *)); + add_inherited_target_child_roots(root); + } + + /* * Ready to do the primary planning. */ final_rel = make_one_rel(root, joinlist); - /* Check that we got at least one usable path */ - if (!final_rel || !final_rel->cheapest_total_path || - final_rel->cheapest_total_path->param_info != NULL) + /* + * Check that we got at least one usable path. In the case of an + * inherited update/delete operation, no path has been created for + * the query's actual target relation yet. + */ + if (!inherited_update && + (!final_rel || + !final_rel->cheapest_total_path || + final_rel->cheapest_total_path->param_info != NULL)) elog(ERROR, "failed to construct the join relation"); return final_rel; } + +/* + * add_inherited_target_child_roots + * Add PlannerInfos for inheritance target children + */ +static void +add_inherited_target_child_roots(PlannerInfo *root) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; + + Assert(root->inh_target_child_roots != NULL); + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + RangeTblEntry *childRTE; + PlannerInfo *subroot; + + if (appinfo->parent_relid != resultRelation) + continue; + + childRTE = root->simple_rte_array[appinfo->child_relid]; + subroot = adjust_inherited_target_child_root(root, appinfo); + + if (childRTE->inh) + add_inherited_target_child_roots(subroot); + + root->inh_target_child_roots[appinfo->child_relid] = subroot; + } +} + +/* + * add_inherit_target_child_root + * This translates query to match the child given by appinfo and + * puts it in a PlannerInfo that will be used for planning the child + * + * The child PlannerInfo reuses most of the parent PlannerInfo's fields + * unchanged, except unexpanded_tlist and processed_tlist are based on the + * child relation. + */ +static PlannerInfo * +adjust_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) +{ + PlannerInfo *subroot; + List *tlist; + ListCell *lc; + + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + + /* Translate the original query's expressions to this child. */ + subroot = makeNode(PlannerInfo); + memcpy(subroot, root, sizeof(PlannerInfo)); + + /* + * Restore the unexpanded tlist for translation, so that child's + * query contains targetList numbered (resnos) per its own + * TupleDesc, which adjust_inherited_tlist ensures. + */ + root->parse->targetList = root->unexpanded_tlist; + subroot->parse = (Query *) adjust_appendrel_attrs(root, + (Node *) root->parse, + 1, &appinfo); + + /* + * Save the original unexpanded targetlist in child subroot, just as + * we did for the parent root, so that this child's own children can use + * it. Must use copy because subroot->parse->targetList will be modified + * soon. + */ + subroot->unexpanded_tlist = list_copy(subroot->parse->targetList); + + /* + * Apply planner's expansion of targetlist, such as adding various junk + * column, filling placeholder entries for dropped columns, etc., all of + * which occurs with the child's TupleDesc. + */ + tlist = preprocess_targetlist(subroot); + subroot->processed_tlist = tlist; + + /* Add any newly added Vars to the child RelOptInfo. */ + build_base_rel_tlists(subroot, tlist); + + /* + * Adjust all_baserels to replace the original target relation with the + * child target relation. Copy it before modifying though. + */ + subroot->all_baserels = bms_copy(root->all_baserels); + subroot->all_baserels = bms_del_member(subroot->all_baserels, + root->parse->resultRelation); + subroot->all_baserels = bms_add_member(subroot->all_baserels, + subroot->parse->resultRelation); + + /* + * Child root should get its own copy of ECs, because they'll be modified + * to replace parent EC expressions by child expressions in + * add_child_rel_equivalences. + */ + subroot->eq_classes = NIL; + foreach(lc, root->eq_classes) + { + EquivalenceClass *ec = lfirst(lc); + EquivalenceClass *new_ec = makeNode(EquivalenceClass); + + memcpy(new_ec, ec, sizeof(EquivalenceClass)); + new_ec->ec_members = list_copy(ec->ec_members); + subroot->eq_classes = lappend(subroot->eq_classes, new_ec); + } + + return subroot; +} diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 3e33a17a5b..2fb520f1fb 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -37,6 +37,7 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "nodes/relation.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" @@ -126,7 +127,7 @@ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static void inheritance_planner(PlannerInfo *root); static void grouping_planner(PlannerInfo *root, bool inheritance_update, - double tuple_fraction); + RelOptInfo *planned_rel, double tuple_fraction); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, int *tleref_to_colnum_map); @@ -630,7 +631,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->grouping_map = NULL; root->minmax_aggs = NIL; root->qual_security_level = 0; - root->inhTargetKind = INHKIND_NONE; root->hasRecursion = hasRecursion; if (hasRecursion) root->wt_param_id = SS_assign_special_param(root); @@ -970,7 +970,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh) inheritance_planner(root); else - grouping_planner(root, false, tuple_fraction); + grouping_planner(root, false, NULL, tuple_fraction); /* * Capture the set of outer-level param IDs we have access to, for use in @@ -1146,12 +1146,19 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr) * inheritance set. * * We have to handle this case differently from cases where a source relation - * is an inheritance set. Source inheritance is expanded at the bottom of the - * plan tree (see allpaths.c), but target inheritance has to be expanded at - * the top. The reason is that for UPDATE, each target relation needs a - * different targetlist matching its own column set. Fortunately, - * the UPDATE/DELETE target can never be the nullable side of an outer join, - * so it's OK to generate the plan this way. + * is an inheritance set. That's mainly because for UPDATE, each target + * relation needs a different targetlist matching its own column set. So, we + * must modify the source scan/join path for each target relation such that it + * produces the desired target list. + * + * The source scan/join paths for individual target relations are still + * created in allpaths.c by first expanding the inheritance in the usual way + * by set_append_rel_size, followed by join planning for each target relation + * separately in make_one_rel. Finally, we apply grouping_planner here to each + * child scan/join path so that it produces the desired targetlist. + * + * Fortunately, the UPDATE/DELETE target can never be the nullable side of an + * outer join, so it's OK to generate the plan this way. * * Returns nothing; the useful output is in the Paths we attach to * the (UPPERREL_FINAL, NULL) upperrel stored in *root. @@ -1164,14 +1171,8 @@ inheritance_planner(PlannerInfo *root) { Query *parse = root->parse; int top_parentRTindex = parse->resultRelation; - Bitmapset *subqueryRTindexes; - Bitmapset *modifiableARIindexes; int nominalRelation = -1; Index rootRelation = 0; - List *final_rtable = NIL; - int save_rel_array_size = 0; - RelOptInfo **save_rel_array = NULL; - AppendRelInfo **save_append_rel_array = NULL; List *subpaths = NIL; List *subroots = NIL; List *resultRelations = NIL; @@ -1179,70 +1180,59 @@ inheritance_planner(PlannerInfo *root) List *returningLists = NIL; List *rowMarks; RelOptInfo *final_rel; - ListCell *lc; - Index rti; + ListCell *lc1, + *lc2; RangeTblEntry *parent_rte; - PlannerInfo *parent_root; - Query *parent_parse; - Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); - PlannerInfo **parent_roots = NULL; + List *tlist; + standard_qp_extra qp_extra; + RelOptInfo *planned_rel; + /* Inheritance is never used for insert. */ Assert(parse->commandType != CMD_INSERT); + parent_rte = planner_rt_fetch(top_parentRTindex, root); /* - * We generate a modified instance of the original Query for each target - * relation, plan that, and put all the plans into a list that will be - * controlled by a single ModifyTable node. All the instances share the - * same rangetable, but each instance must have its own set of subquery - * RTEs within the finished rangetable because (1) they are likely to get - * scribbled on during planning, and (2) it's not inconceivable that - * subqueries could get planned differently in different cases. We need - * not create duplicate copies of other RTE kinds, in particular not the - * target relations, because they don't have either of those issues. Not - * having to duplicate the target relations is important because doing so - * (1) would result in a rangetable of length O(N^2) for N targets, with - * at least O(N^3) work expended here; and (2) would greatly complicate - * management of the rowMarks list. + * Generate the access paths for all relations mentioned in the query, + * including the target inheritance set. When doing the join planning, + * references in the join tree to the original target relation that's the + * root parent of the inheritance tree is replaced by each of its + * inheritance children and the resulting joinrel RelOptInfo's are + * added to root->inh_target_child_rels. * - * To begin with, generate a bitmapset of the relids of the subquery RTEs. + * Final planning steps (grouping_planner) are applied to the best path + * of each of those child joinrels using a modified instance of the + * original query for a given child target rel. All the paths so + * generated are put into a list that will be controlled by a single + * ModifyTable node. All the instances share the same rangetable. */ - subqueryRTindexes = NULL; - rti = 1; - foreach(lc, parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - if (rte->rtekind == RTE_SUBQUERY) - subqueryRTindexes = bms_add_member(subqueryRTindexes, rti); - rti++; - } /* - * Next, we want to identify which AppendRelInfo items contain references - * to any of the aforesaid subquery RTEs. These items will need to be - * copied and modified to adjust their subquery references; whereas the - * other ones need not be touched. It's worth being tense over this - * because we can usually avoid processing most of the AppendRelInfo - * items, thereby saving O(N^2) space and time when the target is a large - * inheritance tree. We can identify AppendRelInfo items by their - * child_relid, since that should be unique within the list. + * Save the unexpanded version of the query's targetlist to be used below + * for passing to grouping_planner for each child target relation. */ - modifiableARIindexes = NULL; - if (subqueryRTindexes != NULL) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); + root->unexpanded_tlist = list_copy(root->parse->targetList); - if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) || - bms_is_member(appinfo->child_relid, subqueryRTindexes) || - bms_overlap(pull_varnos((Node *) appinfo->translated_vars), - subqueryRTindexes)) - modifiableARIindexes = bms_add_member(modifiableARIindexes, - appinfo->child_relid); - } + /* Do the scan/join planning. */ + tlist = preprocess_targetlist(root); + root->processed_tlist = tlist; + qp_extra.tlist = tlist; + qp_extra.activeWindows = qp_extra.groupClause = NIL; + planned_rel = query_planner(root, tlist, standard_qp_callback, &qp_extra); + + /* + * If it turned out during query planning that all the children are dummy + * (pruned or excluded by constraints), no need to do the steps below. + * Let grouping_planner finish up the final path. + */ + if (IS_DUMMY_REL(planned_rel)) + { + grouping_planner(root, false, planned_rel, 0.0); + return; } + Assert(planned_rel->relid == top_parentRTindex); + Assert(planned_rel->reloptkind == RELOPT_BASEREL); + /* * If the parent RTE is a partitioned table, we should use that as the * nominal target relation, because the RTEs added for partitioned tables @@ -1250,7 +1240,6 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1258,95 +1247,42 @@ inheritance_planner(PlannerInfo *root) } /* - * The PlannerInfo for each child is obtained by translating the relevant - * members of the PlannerInfo for its immediate parent, which we find - * using the parent_relid in its AppendRelInfo. We save the PlannerInfo - * for each parent in an array indexed by relid for fast retrieval. Since - * the maximum number of parents is limited by the number of RTEs in the - * query, we use that number to allocate the array. An extra entry is - * needed since relids start from 1. + * Get on with finalizing the path for each child target relation by + * calling grouping_planner on its joinrel. Note that we're restoring + * the query's targetlist to the original one for grouping_planner's + * targetlist expansion steps to perform the expansion with individual + * child descriptors. */ - parent_roots = (PlannerInfo **) palloc0((list_length(parse->rtable) + 1) * - sizeof(PlannerInfo *)); - parent_roots[top_parentRTindex] = root; - - /* - * And now we can get on with generating a plan for each child table. - */ - foreach(lc, root->append_rel_list) + forboth(lc1, root->inh_target_child_rels, + lc2, root->inh_target_child_joinrels) { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - PlannerInfo *subroot; + Index childRTindex = lfirst_int(lc1); + RelOptInfo *childrel = lfirst(lc2); + PlannerInfo *subroot = root->inh_target_child_roots[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; RangeTblEntry *child_rte; RelOptInfo *sub_final_rel; Path *subpath; - /* append_rel_list contains all append rels; ignore others */ - if (!bms_is_member(appinfo->parent_relid, parent_relids)) - continue; - - /* - * expand_inherited_rtentry() always processes a parent before any of - * that parent's children, so the parent_root for this relation should - * already be available. - */ - parent_root = parent_roots[appinfo->parent_relid]; - Assert(parent_root != NULL); - parent_parse = parent_root->parse; - - /* - * We need a working copy of the PlannerInfo so that we can control - * propagation of information back to the main copy. - */ - subroot = makeNode(PlannerInfo); - memcpy(subroot, parent_root, sizeof(PlannerInfo)); - - /* - * Generate modified query with this rel as target. We first apply - * adjust_appendrel_attrs, which copies the Query and changes - * references to the parent RTE to refer to the current child RTE, - * then fool around with subquery RTEs. - */ - subroot->parse = (Query *) - adjust_appendrel_attrs(parent_root, - (Node *) parent_parse, - 1, &appinfo); + Assert(subroot != NULL); + Assert(subroot->parse->resultRelation == childRTindex); /* * If there are securityQuals attached to the parent, move them to the * child rel (they've already been transformed properly for that). */ - parent_rte = rt_fetch(appinfo->parent_relid, subroot->parse->rtable); - child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable); + parent_rte = planner_rt_fetch(appinfo->parent_relid, subroot); + child_rte = planner_rt_fetch(appinfo->child_relid, subroot); child_rte->securityQuals = parent_rte->securityQuals; parent_rte->securityQuals = NIL; /* - * Mark whether we're planning a query to a partitioned table or an - * inheritance parent. + * Ignore a partitioned child. Instead, the paths of its children will + * be added to subpaths. */ - subroot->inhTargetKind = - (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED; - - /* - * If this child is further partitioned, remember it as a parent. - * Since a partitioned table does not have any data, we don't need to - * create a plan for it, and we can stop processing it here. We do, - * however, need to remember its modified PlannerInfo for use when - * processing its children, since we'll update their varnos based on - * the delta from immediate parent to child, not from top to child. - * - * Note: a very non-obvious point is that we have not yet added - * duplicate subquery RTEs to the subroot's rtable. We mustn't, - * because then its children would have two sets of duplicates, - * confusing matters. - */ - if (child_rte->inh) + if (childrel->part_scheme) { - Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE); - parent_relids = bms_add_member(parent_relids, appinfo->child_relid); - parent_roots[appinfo->child_relid] = subroot; - + Assert(child_rte->inh); continue; } @@ -1374,111 +1310,8 @@ inheritance_planner(PlannerInfo *root) if (nominalRelation < 0) nominalRelation = appinfo->child_relid; - /* - * The rowMarks list might contain references to subquery RTEs, so - * make a copy that we can apply ChangeVarNodes to. (Fortunately, the - * executor doesn't need to see the modified copies --- we can just - * pass it the original rowMarks list.) - */ - subroot->rowMarks = copyObject(parent_root->rowMarks); - - /* - * The append_rel_list likewise might contain references to subquery - * RTEs (if any subqueries were flattenable UNION ALLs). So prepare - * to apply ChangeVarNodes to that, too. As explained above, we only - * want to copy items that actually contain such references; the rest - * can just get linked into the subroot's append_rel_list. - * - * If we know there are no such references, we can just use the outer - * append_rel_list unmodified. - */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - subroot->append_rel_list = NIL; - foreach(lc2, parent_root->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, modifiableARIindexes)) - appinfo2 = copyObject(appinfo2); - - subroot->append_rel_list = lappend(subroot->append_rel_list, - appinfo2); - } - } - - /* - * Add placeholders to the child Query's rangetable list to fill the - * RT indexes already reserved for subqueries in previous children. - * These won't be referenced, so there's no need to make them very - * valid-looking. - */ - while (list_length(subroot->parse->rtable) < list_length(final_rtable)) - subroot->parse->rtable = lappend(subroot->parse->rtable, - makeNode(RangeTblEntry)); - - /* - * If this isn't the first child Query, generate duplicates of all - * subquery RTEs, and adjust Var numbering to reference the - * duplicates. To simplify the loop logic, we scan the original rtable - * not the copy just made by adjust_appendrel_attrs; that should be OK - * since subquery RTEs couldn't contain any references to the target - * rel. - */ - if (final_rtable != NIL && subqueryRTindexes != NULL) - { - ListCell *lr; - - rti = 1; - foreach(lr, parent_parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lr); - - if (bms_is_member(rti, subqueryRTindexes)) - { - Index newrti; - - /* - * The RTE can't contain any references to its own RT - * index, except in its securityQuals, so we can save a - * few cycles by applying ChangeVarNodes to the rest of - * the rangetable before we append the RTE to it. - */ - newrti = list_length(subroot->parse->rtable) + 1; - ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); - ChangeVarNodes((Node *) subroot->rowMarks, rti, newrti, 0); - /* Skip processing unchanging parts of append_rel_list */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - foreach(lc2, subroot->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, - modifiableARIindexes)) - ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); - } - } - rte = copyObject(rte); - ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); - subroot->parse->rtable = lappend(subroot->parse->rtable, - rte); - } - rti++; - } - } - - /* There shouldn't be any OJ info to translate, as yet */ - Assert(subroot->join_info_list == NIL); - /* and we haven't created PlaceHolderInfos, either */ - Assert(subroot->placeholder_list == NIL); - - /* Generate Path(s) for accessing this result relation */ - grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ ); + /* Apply the top-level targetlist to childrel's path. */ + grouping_planner(subroot, true, childrel, 0.0); /* * Select cheapest path in case there's more than one. We always run @@ -1490,45 +1323,10 @@ inheritance_planner(PlannerInfo *root) subpath = sub_final_rel->cheapest_total_path; /* - * If this child rel was excluded by constraint exclusion, exclude it - * from the result plan. + * child rel cannot be empty, or inheritance_make_rel_from_joinlist + * wouldn't have put it in the list to begin with. */ - if (IS_DUMMY_PATH(subpath)) - continue; - - /* - * If this is the first non-excluded child, its post-planning rtable - * becomes the initial contents of final_rtable; otherwise, append - * just its modified subquery RTEs to final_rtable. - */ - if (final_rtable == NIL) - final_rtable = subroot->parse->rtable; - else - final_rtable = list_concat(final_rtable, - list_copy_tail(subroot->parse->rtable, - list_length(final_rtable))); - - /* - * We need to collect all the RelOptInfos from all child plans into - * the main PlannerInfo, since setrefs.c will need them. We use the - * last child's simple_rel_array (previous ones are too short), so we - * have to propagate forward the RelOptInfos that were already built - * in previous children. - */ - Assert(subroot->simple_rel_array_size >= save_rel_array_size); - for (rti = 1; rti < save_rel_array_size; rti++) - { - RelOptInfo *brel = save_rel_array[rti]; - - if (brel) - subroot->simple_rel_array[rti] = brel; - } - save_rel_array_size = subroot->simple_rel_array_size; - save_rel_array = subroot->simple_rel_array; - save_append_rel_array = subroot->append_rel_array; - - /* Make sure any initplans from this rel get into the outer list */ - root->init_plans = subroot->init_plans; + Assert(!IS_DUMMY_PATH(subpath)); /* Build list of sub-paths */ subpaths = lappend(subpaths, subpath); @@ -1560,36 +1358,6 @@ inheritance_planner(PlannerInfo *root) */ /* - * If we managed to exclude every child rel, return a dummy plan; it - * doesn't even need a ModifyTable node. - */ - if (subpaths == NIL) - { - set_dummy_rel_pathlist(final_rel); - return; - } - - /* - * Put back the final adjusted rtable into the master copy of the Query. - * (We mustn't do this if we found no non-excluded children.) - */ - parse->rtable = final_rtable; - root->simple_rel_array_size = save_rel_array_size; - root->simple_rel_array = save_rel_array; - root->append_rel_array = save_append_rel_array; - - /* Must reconstruct master's simple_rte_array, too */ - root->simple_rte_array = (RangeTblEntry **) - palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *)); - rti = 1; - foreach(lc, final_rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - root->simple_rte_array[rti++] = rte; - } - - /* * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will * have dealt with fetching non-locked marked rows, else we need to have * ModifyTable do that. @@ -1629,6 +1397,12 @@ inheritance_planner(PlannerInfo *root) * (inheritance_planner will create a single ModifyTable node covering all the * target tables.) * + * If non-NULL, planned_rel is a RelOptInfo containing paths for the query's + * top-level joinrel, which the caller produced by itself. In that case, this + * function only needs to adjust the targetlist of its cheapest_total_path. + * The only caller that may pass such a RelOptInfo currently is + * inheritance_planner. + * * tuple_fraction is the fraction of tuples we expect will be retrieved. * tuple_fraction is interpreted as follows: * 0: expect all tuples to be retrieved (normal case) @@ -1647,6 +1421,7 @@ inheritance_planner(PlannerInfo *root) */ static void grouping_planner(PlannerInfo *root, bool inheritance_update, + RelOptInfo *planned_rel, double tuple_fraction) { Query *parse = root->parse; @@ -1659,7 +1434,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, List *final_targets; List *final_targets_contain_srfs; bool final_target_parallel_safe; - RelOptInfo *current_rel; + RelOptInfo *current_rel = planned_rel; RelOptInfo *final_rel; ListCell *lc; @@ -1699,6 +1474,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * special work for recursive unions is the responsibility of * plan_set_operations. */ + Assert(current_rel == NULL); current_rel = plan_set_operations(root); /* @@ -1788,17 +1564,26 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, parse->groupClause = preprocess_groupclause(root, NIL); } - /* Preprocess targetlist */ - tlist = preprocess_targetlist(root); - /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation so - * that we can transfer its decoration (resnames etc) to the topmost - * tlist of the finished Plan. + * Preprocess targetlist, if needed. If the caller has already done + * query planning, root->processed_tlist already contains the desired + * targetlist. */ - root->processed_tlist = tlist; + if (planned_rel == NULL) + { + tlist = preprocess_targetlist(root); + + /* + * We are now done hacking up the query's targetlist. Most of the + * remaining planning work will be done with the PathTarget + * representation of tlists, but save aside the full representation + * so that we can transfer its decoration (resnames etc) to the + * topmost tlist of the finished Plan. + */ + root->processed_tlist = tlist; + } + else + tlist = root->processed_tlist; /* * Collect statistics about aggregates for estimating costs, and mark @@ -1878,8 +1663,9 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * We also generate (in standard_qp_callback) pathkey representations * of the query's sort clause, distinct clause, etc. */ - current_rel = query_planner(root, tlist, - standard_qp_callback, &qp_extra); + if (current_rel == NULL) + current_rel = query_planner(root, tlist, + standard_qp_callback, &qp_extra); /* * Convert the query's result tlist into PathTarget format. diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 77dbf4eba3..87adfa02b1 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -917,7 +917,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->grouping_map = NULL; subroot->minmax_aggs = NIL; subroot->qual_security_level = 0; - subroot->inhTargetKind = INHKIND_NONE; subroot->hasRecursion = false; subroot->wt_param_id = -1; subroot->non_recursive_path = NULL; diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 1d280c205e..6b62f8e9cf 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -2250,8 +2250,39 @@ adjust_appendrel_attrs_mutator(Node *node, context->appinfos); return (Node *) phv; } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 48ffc5f254..51d8faf2dd 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1264,36 +1264,6 @@ get_relation_constraints(PlannerInfo *root, } } - /* - * Append partition predicates, if any. - * - * For selects, partition pruning uses the parent table's partition bound - * descriptor, instead of constraint exclusion which is driven by the - * individual partition's partition constraint. - */ - if (enable_partition_pruning && root->parse->commandType != CMD_SELECT) - { - List *pcqual = RelationGetPartitionQual(relation); - - if (pcqual) - { - /* - * Run the partition quals through const-simplification similar to - * check constraints. We skip canonicalize_qual, though, because - * partition quals should be in canonical form already; also, - * since the qual is in implicit-AND format, we'd have to - * explicitly convert it to explicit-AND format and back again. - */ - pcqual = (List *) eval_const_expressions(root, (Node *) pcqual); - - /* Fix Vars to have the desired varno */ - if (varno != 1) - ChangeVarNodes((Node *) pcqual, 1, varno, 0); - - result = list_concat(result, pcqual); - } - } - heap_close(relation, NoLock); return result; @@ -1420,31 +1390,15 @@ relation_excluded_by_constraints(PlannerInfo *root, switch (constraint_exclusion) { case CONSTRAINT_EXCLUSION_OFF: - - /* - * Don't prune if feature turned off -- except if the relation is - * a partition. While partprune.c-style partition pruning is not - * yet in use for all cases (update/delete is not handled), it - * would be a UI horror to use different user-visible controls - * depending on such a volatile implementation detail. Therefore, - * for partitioned tables we use enable_partition_pruning to - * control this behavior. - */ - if (root->inhTargetKind == INHKIND_PARTITIONED) - break; return false; case CONSTRAINT_EXCLUSION_PARTITION: /* * When constraint_exclusion is set to 'partition' we only handle - * OTHER_MEMBER_RELs, or BASERELs in cases where the result target - * is an inheritance parent or a partitioned table. + * OTHER_MEMBER_RELs. */ - if ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) && - !(rel->reloptkind == RELOPT_BASEREL && - root->inhTargetKind != INHKIND_NONE && - rel->relid == root->parse->resultRelation)) + if (rel->reloptkind != RELOPT_OTHER_MEMBER_REL) return false; break; diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 3430061361..2fde95de7b 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -319,9 +319,6 @@ typedef struct PlannerInfo Index qual_security_level; /* minimum security_level for quals */ /* Note: qual_security_level is zero if there are no securityQuals */ - InheritanceKind inhTargetKind; /* indicates if the target relation is an - * inheritance child or partition or a - * partitioned table */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */ bool hasDeletedRTEs; /* true if any RTE was deleted from jointree */ @@ -343,6 +340,37 @@ typedef struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* + * The following fields are set during query planning portion of an + * inherited UPDATE/DEELETE operation. + */ + + /* + * This stores the original version of the query's targetlist that's + * not modified by the planner. + */ + List *unexpanded_tlist; + + /* + * PlannerInfos corresponding to each inheritance child targets. + * Content of each PlannerInfo is same as the parent PlannerInfo, except + * for the parse tree which is a translated copy of the parent's parse + * tree. + */ + struct PlannerInfo **inh_target_child_roots; + + /* List of RT indexes of child RT indexes. */ + List *inh_target_child_rels; + + /* + * RelOptInfos corresponding to each child target rel. For leaf children, + * it's the RelOptInfo representing the output of make_rel_from_joinlist() + * called with the parent rel in the original join tree replaced by a + * given leaf child. For non-leaf children, it's the baserel RelOptInfo + * itself, left as a placeholder. + */ + List *inh_target_child_joinrels; } PlannerInfo; diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 666217c189..a39a0a3a74 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -161,7 +161,8 @@ extern EquivalenceClass *match_eclasses_to_foreign_key_col(PlannerInfo *root, extern void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, - RelOptInfo *child_rel); + RelOptInfo *child_rel, + bool child_is_target); extern List *generate_implied_equalities_for_column(PlannerInfo *root, RelOptInfo *rel, ec_matches_callback_type callback, diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index c55de5d476..c9f02c1fb0 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1780,7 +1780,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_1 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_1 -> Nested Loop Semi Join @@ -1788,7 +1788,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_2 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_2 (28 rows) -- 2.11.0
v11-0002-Store-inheritance-root-parent-index-in-otherrel-.patch (text/plain, 2.5K) download From 398a793c14de90729730ffb926ebc5f42b6e9b9f Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 26 Oct 2018 16:45:59 +0900 Subject: [PATCH v11 2/6] Store inheritance root parent index in otherrel's RelOptInfo Although it's set by build_simple_rel, it's not being used by any code yet. --- src/backend/nodes/outfuncs.c | 1 + src/backend/optimizer/util/relnode.c | 14 ++++++++++++++ src/include/nodes/relation.h | 4 ++++ 3 files changed, 19 insertions(+) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index f6112e1b4c..5248b5a764 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2271,6 +2271,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_BOOL_FIELD(consider_partitionwise_join); WRITE_BITMAPSET_FIELD(top_parent_relids); WRITE_NODE_FIELD(partitioned_child_rels); + WRITE_UINT_FIELD(inh_root_parent); } static void diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 111b42d654..e079a2469d 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -215,9 +215,23 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->top_parent_relids = parent->top_parent_relids; else rel->top_parent_relids = bms_copy(parent->relids); + + /* + * For inheritance child relations, we also set inh_root_parent. + * Note that 'parent' might itself be a child (a sub-partitioned + * partition), in which case we simply use its value of + * inh_root_parent. + */ + if (parent->rtekind == RTE_RELATION) + rel->inh_root_parent = parent->inh_root_parent > 0 ? + parent->inh_root_parent : + parent->relid; } else + { rel->top_parent_relids = NULL; + rel->inh_root_parent = 0; + } /* Check type of rtable entry */ switch (rte->rtekind) diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 2fde95de7b..a2adbfc3b8 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -731,6 +731,10 @@ typedef struct RelOptInfo List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ + + Index inh_root_parent; /* For otherrels, this is the RT index of + * inheritance table mentioned in the query + * from which this relation originated */ } RelOptInfo; /* -- 2.11.0
v11-0003-Lazy-creation-of-RTEs-for-inheritance-children.patch (text/plain, 91.5K) download From af059709d568dafc2409db2b3038e773be3f72d8 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 18:14:13 +0900 Subject: [PATCH v11 3/6] Lazy creation of RTEs for inheritance children Currently, expand_inherited_tables called from subquery_planner opens all inheritance child tables and adds them to the query's range table and PlannerInfo in the form of AppendRelInfo's initially and later RelOptInfo's. For partitioning, that's pretty wasteful, because it's possible to determine only the partitions that will need to be scanned using partition pruning, which doesn't require opening the partitions themselves. However, the point at which the above processing happens is too early to perform partition pruning. This commit rearranges things within the planner so that inheritance child relations can be added to the Query and PlannerInfo during make_one_rel, just before set_base_rel_sizes is called. Although the late initialization approach only benefits the partitioning case, it seems undesirable to do it only for partitioned tables, because it means adding code to handle partitions specially at various places within the planner. So, *all* inheritance parent tables are expanded in make_one_rel. All unpruned partitions are added to Query in the form of their RangeTblEntry's being added to the range table and to PlannerInfo in the form of an AppendRelInfo and a RelOptInfo for each. For partitioning, although we don't create a RangeTblEntry and RelOptInfo for pruned partitions at make_one_rel time, partitionwise join code relies on the fact that even though partitions may have been pruned, they'd still own a RelOptInfo to handle the outer join case where the pruned partition appears on the nullable side of join. Partitionwise join code deals with that by allocating dummy RelOptInfos for pruned partitions that are based mostly on their parent's properties. There are some regression test diffs: 1. Caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child, as seen in the partition_aggregate.out test output. 2. Those in postgres_fdw.out are caused by the fact that junk columns required for row marking are added to reltarget->exprs later than user columns, because the row marking junk columns arent added until after the inheritance is expanded which as of this commit is later than it used to be. --- contrib/postgres_fdw/expected/postgres_fdw.out | 32 +- src/backend/optimizer/path/allpaths.c | 355 +-------- src/backend/optimizer/path/joinrels.c | 62 ++ src/backend/optimizer/plan/initsplan.c | 58 -- src/backend/optimizer/plan/planmain.c | 12 +- src/backend/optimizer/plan/planner.c | 65 +- src/backend/optimizer/plan/setrefs.c | 6 + src/backend/optimizer/prep/preptlist.c | 15 +- src/backend/optimizer/prep/prepunion.c | 854 +++++++++++++++------- src/backend/optimizer/util/Makefile | 2 +- src/backend/optimizer/util/plancat.c | 40 +- src/backend/optimizer/util/relnode.c | 80 +- src/backend/partitioning/partprune.c | 43 +- src/include/nodes/relation.h | 8 + src/include/optimizer/pathnode.h | 1 + src/include/optimizer/plancat.h | 2 +- src/include/optimizer/prep.h | 9 +- src/include/partitioning/partprune.h | 2 +- src/test/regress/expected/partition_aggregate.out | 4 +- 19 files changed, 886 insertions(+), 764 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index bb92d9d37a..33a33606ef 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7072,15 +7072,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7110,15 +7110,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7147,15 +7147,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid @@ -7165,15 +7165,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (39 rows) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 1e1dfaac47..be7b8a717e 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -44,7 +44,6 @@ #include "optimizer/var.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -378,7 +377,11 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, */ set_dummy_rel_pathlist(rel); } - else if (rte->inh) + /* + * expand_inherited_tables may have proved that the relation is empty, so + * check if it's so. + */ + else if (rte->inh && !IS_DUMMY_REL(rel)) { /* * If it's a target relation, set the sizes of children instead. @@ -949,38 +952,18 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, int parentRTindex = rti; ListCell *l; bool has_live_children; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); Assert(IS_SIMPLE_REL(rel)); - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - foreach(l, root->append_rel_list) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; PlannerInfo *subroot; ListCell *lc; List *translated_exprs, @@ -995,139 +978,18 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, subroot = root->inh_target_child_roots[childRTindex]; /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo is created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals. */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); + if (IS_DUMMY_REL(childrel)) continue; - } - - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } if (relation_excluded_by_constraints(root, childrel, childRTE)) { @@ -1269,8 +1131,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -1278,32 +1138,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Assert(IS_SIMPLE_REL(rel)); /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, we will bubble up - * the indexes of partitioned relations that appear down in the tree, so - * that when we've created Paths for all the children, the root - * partitioned table's list will contain all such indexes. - */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - rel->partitioned_child_rels = list_make1_int(rti); - - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - - /* * If this is a partitioned baserel, set the consider_partitionwise_join * flag; currently, we only consider partitionwise joins with the baserel * if its targetlist doesn't contain a whole-row Var. @@ -1340,12 +1174,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; ListCell *parentvars; ListCell *childvars; - ListCell *lc; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1353,18 +1183,34 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo is created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); /* - * Copy/Modify targetlist. Even if this child is deemed empty, we need - * its targetlist in case it falls on nullable side in a child-join - * because of partitionwise join. + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals. + */ + if (IS_DUMMY_REL(childrel)) + continue; + + if (relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* + * This child need not be scanned, so we can omit it from the + * appendrel. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Copy/Modify targetlist. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -1394,144 +1240,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, false); childrel->has_eclass_joins = rel->has_eclass_joins; - /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* - * This child need not be scanned, so we can omit it from the - * appendrel. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - /* CE failed, so finish copying/modifying join quals. */ childrel->joininfo = (List *) adjust_appendrel_attrs(root, @@ -3068,6 +2776,7 @@ inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) subroot = root->inh_target_child_roots[appinfo->child_relid]; Assert(subroot->parse->resultRelation > 0); childrel = find_base_rel(root, appinfo->child_relid); + Assert(childrel != NULL); /* Ignore excluded/pruned children. */ if (IS_DUMMY_REL(childrel)) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 67b9ca83a7..be2e39fd4b 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -20,6 +20,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/prep.h" +#include "optimizer/tlist.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -46,6 +47,9 @@ static void try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, List *parent_restrictlist); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + int partidx); /* @@ -1376,6 +1380,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts); + if (child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts); + /* We should never try to join two overlapping sets of rels. */ Assert(!bms_overlap(child_rel1->relids, child_rel2->relids)); child_joinrelids = bms_union(child_rel1->relids, child_rel2->relids); @@ -1582,3 +1591,56 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx) +{ + RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + /* Pass parent's info as for both the parent rel and child rel. */ + AppendRelInfo *appinfo = make_append_rel_info(parent, parentrte, + parent->tupdesc, + parentrte->relid, + parent->reltype, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index a66374094f..c628f3d9e7 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -616,64 +616,6 @@ create_lateral_join_info(PlannerInfo *root) bms_add_member(brel2->lateral_referencers, rti); } } - - /* - * Lastly, propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - */ - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - RangeTblEntry *brte = root->simple_rte_array[rti]; - - /* - * Skip empty slots. Also skip non-simple relations i.e. dead - * relations. - */ - if (brel == NULL || !IS_SIMPLE_REL(brel)) - continue; - - /* - * In the case of table inheritance, the parent RTE is directly linked - * to every child table via an AppendRelInfo. In the case of table - * partitioning, the inheritance hierarchy is expanded one level at a - * time rather than flattened. Therefore, an other member rel that is - * a partitioned table may have children of its own, and must - * therefore be marked with the appropriate lateral info so that those - * children eventually get marked also. - */ - Assert(brte); - if (brel->reloptkind == RELOPT_OTHER_MEMBER_REL && - (brte->rtekind != RTE_RELATION || - brte->relkind != RELKIND_PARTITIONED_TABLE)) - continue; - - if (brte->inh) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); - RelOptInfo *childrel; - - if (appinfo->parent_relid != rti) - continue; - childrel = root->simple_rel_array[appinfo->child_relid]; - Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - Assert(childrel->direct_lateral_relids == NULL); - childrel->direct_lateral_relids = brel->direct_lateral_relids; - Assert(childrel->lateral_relids == NULL); - childrel->lateral_relids = brel->lateral_relids; - Assert(childrel->lateral_referencers == NULL); - childrel->lateral_referencers = brel->lateral_referencers; - } - } - } } diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 04238ef047..3f060927cc 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -256,6 +256,16 @@ query_planner(PlannerInfo *root, List *tlist, root->all_baserels = bms_add_member(root->all_baserels, brel->relid); } + /* + * Expand RT entries that represent inherited or partitioned tables. + * This will perform partition pruning on partitioned tables in the + * original range table and also recursively on any child partitioned + * tables that were added by the expansion of the original parent(s). + * As new entries are added to the range table, various arrays in the + * PlannerInfo will be expanded accordingly. + */ + expand_inherited_tables(root); + inherited_update = (parse->resultRelation && root->simple_rte_array[parse->resultRelation]->inh); @@ -367,7 +377,7 @@ adjust_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) * column, filling placeholder entries for dropped columns, etc., all of * which occurs with the child's TupleDesc. */ - tlist = preprocess_targetlist(subroot); + tlist = preprocess_targetlist(subroot, true); subroot->processed_tlist = tlist; /* Add any newly added Vars to the child RelOptInfo. */ diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 2fb520f1fb..b9b56d9280 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -23,6 +23,7 @@ #include "access/sysattr.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -699,27 +700,24 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } /* * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). + * pullup (so that all non-inherited RTEs are present). */ preprocess_rowmarks(root); /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain base relations not joins, so it's OK (and marginally more - * efficient) to do it after checking for join RTEs. We must do it after - * pulling up subqueries, else we'd fail to handle inherited tables in - * subqueries. - */ - expand_inherited_tables(root); - - /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -1212,8 +1210,11 @@ inheritance_planner(PlannerInfo *root) */ root->unexpanded_tlist = list_copy(root->parse->targetList); - /* Do the scan/join planning. */ - tlist = preprocess_targetlist(root); + /* + * Do the scan/join planning. We haven't expanded inheritance yet, so + * pass false. + */ + tlist = preprocess_targetlist(root, false); root->processed_tlist = tlist; qp_extra.tlist = tlist; qp_extra.activeWindows = qp_extra.groupClause = NIL; @@ -1222,9 +1223,10 @@ inheritance_planner(PlannerInfo *root) /* * If it turned out during query planning that all the children are dummy * (pruned or excluded by constraints), no need to do the steps below. - * Let grouping_planner finish up the final path. + * For regular inheritance case, it's possible that we only need to modify + * the parent table. Let grouping_planner finish up the final path. */ - if (IS_DUMMY_REL(planned_rel)) + if (!parent_rte->inh || IS_DUMMY_REL(planned_rel)) { grouping_planner(root, false, planned_rel, 0.0); return; @@ -1571,14 +1573,19 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, */ if (planned_rel == NULL) { - tlist = preprocess_targetlist(root); + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root, false); /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation - * so that we can transfer its decoration (resnames etc) to the - * topmost tlist of the finished Plan. + * We are now mostly done hacking up the query's targetlist. More + * columns might be added during query_planner after inheritance + * expansion in make_one_rel, because some children may require + * different column than the parent for row mark handling; see + * add_rowmark_junk_columns(). Most of the remaining planning + * work will be done with the PathTarget representation of tlists, + * but save aside the full representation so that we can transfer + * its decoration (resnames etc) to the topmost tlist of the + * finished Plan. */ root->processed_tlist = tlist; } @@ -2354,7 +2361,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2379,7 +2386,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } @@ -6816,6 +6823,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -6917,6 +6928,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index fc7ba0dea2..baced8126c 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -325,6 +325,12 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) UPPERREL_FINAL, NULL))) add_rtes_to_flat_rtable(rel->subroot, true); } + /* + * A NULL rel also means an unplanned subquery rte, so apply + * flatten_unplanned_rtes. + */ + else + flatten_unplanned_rtes(glob, rte); } rti++; } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index f8bc2dd257..a55949c305 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -65,9 +65,13 @@ static List *expand_targetlist(List *tlist, int command_type, * * As a side effect, if there's an ON CONFLICT UPDATE clause, its targetlist * is also preprocessed (and updated in-place). + * + * inheritance_expanded specifies whether inheritance tables are expanded + * as of calling this function. Caller must specify it because that affects + * which row marking related junk columns get added to the targetlist. */ List * -preprocess_targetlist(PlannerInfo *root) +preprocess_targetlist(PlannerInfo *root, bool inheritance_expanded) { Query *parse = root->parse; int result_relation = parse->resultRelation; @@ -134,6 +138,15 @@ preprocess_targetlist(PlannerInfo *root) if (rc->rti != rc->prti) continue; + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which will change the parent row + * mark's allMarkTypes fields. + */ + if (rc->isParent && !inheritance_expanded) + continue; + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) { /* Need to fetch TID */ diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 6b62f8e9cf..632781acc2 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -39,15 +39,19 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" +#include "optimizer/var.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" +#include "partitioning/partprune.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/selfuncs.h" @@ -99,23 +103,26 @@ static List *generate_append_tlist(List *colTypes, List *colCollations, List *input_tlists, List *refnames_tlist); static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist); +static void expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti); static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); + Index rti, RelOptInfo *rel); static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); -static void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p); +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); +static RelOptInfo *build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, @@ -1472,33 +1479,134 @@ generate_setop_grouplist(SetOperationStmt *op, List *targetlist) void expand_inherited_tables(PlannerInfo *root) { - Index nrtes; + int orig_rtable_size; Index rti; - ListCell *rl; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; /* - * expand_inherited_rtentry may add RTEs to parse->rtable. The function is + * expand_append_rtentry may add RTEs to parse->rtable. The function is * expected to recursively handle any RTEs that it creates with inh=true. * So just scan as far as the original end of the rtable list. */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) + for (rti = 1; rti < orig_rtable_size; rti++) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + if (rte->inh) + expand_append_rtentry(root, brel, rte, rti); + } +} + +/* + * expand_append_rtentry + * This initializes RelOptInfos for an appendrel's child relations, if + * any + * + * 'rel' is the appendrel parent, whose range table entry ('rte') has been + * marked to require adding children. An appendrel parent could either + * be a subquery (if we flattened UNION ALL query) or a table that's known + * to have inheritance children. The latter consists of both regular + * inheritance parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as an appendrel child. The original RTE is + * considered to represent the whole inheritance set. + */ +static void +expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, + Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_append_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_inherited_rtentry(root, rte, rti, rel); } } /* * expand_inherited_rtentry - * Check whether a rangetable entry represents an inheritance set. - * If so, add entries for all the child tables to the query's - * rangetable, and build AppendRelInfo nodes for all the child tables - * and add them to root->append_rel_list. If not, clear the entry's - * "inh" flag to prevent later code from looking for AppendRelInfos. + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. * * Note that the original RTE is considered to represent the whole * inheritance set. The first of the generated RTEs is an RTE for the same @@ -1509,55 +1617,33 @@ expand_inherited_tables(PlannerInfo *root) * regular inheritance, a parent RTE must always have at least two associated * AppendRelInfos: one corresponding to the parent table as a simple member of * inheritance set and one or more corresponding to the actual children. - * Since a partitioned table is not scanned, it might have only one associated - * AppendRelInfo. */ static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, + RelOptInfo *rel) { Oid parentOID; PlanRowMark *oldrc; - Relation oldrelation; - LOCKMODE lockmode; + LOCKMODE lockmode = rte->rellockmode; List *inhOIDs; ListCell *l; + int num_children; + int num_children_added = 0; - /* Does RT entry allow inheritance? */ - if (!rte->inh) - return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } - /* Fast path for common case of childless table */ + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); parentOID = rte->relid; - if (!has_subclass(parentOID)) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * The rewriter should already have obtained an appropriate lock on each - * relation named in the query. However, for each child relation we add - * to the query, we must obtain an appropriate lock, because this will be - * the first use of those relations in the parse/rewrite/plan pipeline. - * Child rels should use the same lockmode as their parent. - */ - lockmode = rte->rellockmode; /* Scan for all members of inheritance set, acquire needed locks */ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); /* * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite above has_subclass() check, if table - * once had a child but no longer does. + * case. This could happen despite has_subclass() check performed by + * subquery_planner, if table once had a child but no longer does. */ - if (list_length(inhOIDs) < 2) + num_children = list_length(inhOIDs); + if (num_children < 2) { /* Clear flag before returning */ rte->inh = false; @@ -1565,216 +1651,219 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } /* - * If parent relation is selected FOR UPDATE/SHARE, we need to mark its - * PlanRowMark as isParent = true, and generate a new PlanRowMark for each - * child. + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. */ oldrc = get_plan_rowmark(root->rowMarks, rti); - if (oldrc) - oldrc->isParent = true; + Assert(oldrc == NULL || oldrc->isParent); /* - * Must open the parent relation to examine its tupdesc. We need not lock - * it; we assume the rewriter already did. + * Must expand PlannerInfo arrays by num_children before we can add + * children. */ - oldrelation = heap_open(parentOID, NoLock); + expand_planner_arrays(root, num_children); - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) + foreach(l, inhOIDs) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); - - /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. - */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); - } - else - { - List *appinfos = NIL; + Oid childOID = lfirst_oid(l); + Relation newrelation; RangeTblEntry *childrte; Index childRTindex; + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. */ - foreach(l, inhOIDs) + if (RELATION_IS_OTHER_TEMP(newrelation)) { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = heap_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - heap_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - heap_close(newrelation, NoLock); + heap_close(newrelation, lockmode); + continue; } - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); + (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation, &childrte, + &childRTindex); + Assert(childrte != NULL); + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + Assert(childRTindex > 0); + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; } - heap_close(oldrelation, NoLock); + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } } /* * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) + Index parentRTindex, RelOptInfo *parentrel) { + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; - check_stack_depth(); - - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + expand_planner_arrays(root, bms_num_members(partindexes)); /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. */ - if (partdesc->nparts == 0) - { - parentrte->inh = false; - return; - } + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); - for (i = 0; i < partdesc->nparts; i++) + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) { - Oid childOID = partdesc->oids[i]; - Relation childrel; + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + RangeTblEntry *childrte; + Index childRTindex; - /* Open rel; we already have required locks */ - childrel = heap_open(childOID, NoLock); + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation, + &childrte, &childRTindex); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + + /* If the child is partitioned itself, expand it too. */ + if (childrel->part_scheme) + { + Assert(childrte->inh); expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); + childrel); + } + } - /* Close child relation, but keep locks */ - heap_close(childrel, NoLock); + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); } } /* - * expand_single_inheritance_child - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) + * The return value is the RelOptInfo that's added. * * PlanRowMarks still carry the top-parent's RTI, and the top-parent's * allMarkTypes field still accumulates values from all descendents. * * "parentrte" and "parentRTindex" are immediate parent's RTE and * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. */ -static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); Oid childOID = RelationGetRelid(childrel); RangeTblEntry *childrte; Index childRTindex; AppendRelInfo *appinfo; + RelOptInfo *childrelopt; /* * Build an RTE for the child, and attach to query's rangetable list. We * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh = false. Also, set requiredPerms to zero since - * all required permissions checks are done on the original RTE. Likewise, - * set the child's securityQuals to empty, because we only want to apply - * the parent's RLS conditions regardless of what RLS properties + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties * individual children may have. (This is an intentional choice to make * inherited RLS work like regular permissions checks.) The parent * securityQuals will be propagated to children along with other base @@ -1784,55 +1873,42 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childrte_p = childrte; childrte->relid = childOID; childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) - childrte->inh = true; - else - childrte->inh = false; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); childrte->requiredPerms = 0; childrte->securityQuals = NIL; parse->rtable = lappend(parse->rtable, childrte); childRTindex = list_length(parse->rtable); *childRTindex_p = childRTindex; - /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. - */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) - { - appinfo = makeNode(AppendRelInfo); - appinfo->parent_relid = parentRTindex; - appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->rd_rel->reltype; - appinfo->child_reltype = childrel->rd_rel->reltype; - make_inh_translation_list(parentrel, childrel, childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = parentOID; - *appinfos = lappend(*appinfos, appinfo); + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + childrel->rd_att, + childOID, + childrel->rd_rel->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); } /* @@ -1864,6 +1940,24 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need to create one, + * because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_append_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; } /* @@ -1874,14 +1968,11 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, * For paranoia's sake, we match type/collation as well as attribute name. */ static void -make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, - List **translated_vars) +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) { List *vars = NIL; - TupleDesc old_tupdesc = RelationGetDescr(oldrelation); - TupleDesc new_tupdesc = RelationGetDescr(newrelation); - Oid new_relid = RelationGetRelid(newrelation); int oldnatts = old_tupdesc->natts; int newnatts = new_tupdesc->natts; int old_attno; @@ -1911,7 +2002,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, * When we are generating the "translation list" for the parent table * of an inheritance set, no need to search for matches. */ - if (oldrelation == newrelation) + if (from_rel == to_rel) { vars = lappend(vars, makeVar(newvarno, (AttrNumber) (old_attno + 1), @@ -1937,10 +2028,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, { HeapTuple newtup; - newtup = SearchSysCacheAttName(new_relid, attname); + newtup = SearchSysCacheAttName(to_rel, attname); if (!newtup) elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; ReleaseSysCache(newtup); @@ -1950,10 +2041,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, /* Found it, check type and collation match */ if (atttypid != att->atttypid || atttypmod != att->atttypmod) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); if (attcollation != att->attcollation) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); vars = lappend(vars, makeVar(newvarno, (AttrNumber) (new_attno + 1), @@ -2020,6 +2111,255 @@ translate_col_privs(const Bitmapset *parent_privs, } /* + * build_append_child_rel + * Build a RelOptInfo for child relation of an append rel + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields based on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turn out to be self-contradictory, + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + List *childquals; + ListCell *lc; + bool have_const_false_cq; + Index cq_min_security; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = false; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, parent->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual = (Node *) rinfo->clause; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, childqual, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might + * have securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals.) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except + * that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* + * not likely that we'd see constants here, so no + * check + */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, + false, + false, + security_level, + NULL, NULL, + NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* Set child's version of baserestrictinfo. */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; +} + +AppendRelInfo * +make_append_rel_info(RelOptInfo *parentrel, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex) +{ + AppendRelInfo *appinfo = makeNode(AppendRelInfo); + + appinfo->parent_relid = parentrel->relid; + appinfo->child_relid = childRTindex; + appinfo->parent_reltype = parentrel->reltype; + appinfo->child_reltype = childreltype; + make_inh_translation_list(parentrel->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, + &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; + + return appinfo; +} + +/* * adjust_appendrel_attrs * Copy the specified query or expression and translate Vars referring to a * parent rel to refer to the corresponding child rel instead. We also diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile index c54d0a690d..da9ccf32b4 100644 --- a/src/backend/optimizer/util/Makefile +++ b/src/backend/optimizer/util/Makefile @@ -13,6 +13,6 @@ top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global OBJS = clauses.o joininfo.o orclauses.o pathnode.o placeholder.o \ - plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o + plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 51d8faf2dd..6562d3f4ff 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -106,7 +106,7 @@ static void set_baserel_partition_key_exprs(Relation relation, */ void get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) + Bitmapset *updatedCols, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; @@ -444,11 +444,31 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, get_relation_foreign_keys(root, rel, relation, inhparent); /* - * Collect info about relation's partitioning scheme, if any. Only - * inheritance parents may be partitioned. + * Collect some additional information for inheritance parents. */ - if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - set_relation_partition_info(root, rel, relation); + if (inhparent) + { + /* + * We'll need the TupleDesc when initializing the child relation. + * A copy is being made because concurrent changes might drop + * the relcache entry. That's possible because ALTER TABLE + * child_table NO INHERIT parent_table only requires an + * AccessShareLock on parent_table. + */ + rel->tupdesc = CreateTupleDescCopy(RelationGetDescr(relation)); + rel->reltype = RelationGetForm(relation)->reltype; + + /* + * If partitioned, also save the information of partitioning scheme, + * and whether the query updates any of the partition key columns. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + set_relation_partition_info(root, rel, relation); + root->partColsUpdated |= has_partition_attrs(relation, updatedCols, + NULL); + } + } heap_close(relation, NoLock); @@ -1853,16 +1873,20 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; + + /* + * Since we must've taken a lock on the table, it's okay to simply copy + * the pointers to relcache data here. + */ + rel->part_oids = partdesc->oids; + rel->boundinfo = partdesc->boundinfo; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index e079a2469d..fa39afcfe6 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -130,6 +130,37 @@ setup_append_rel_array(PlannerInfo *root) } } +/* Expand the PlannerInfo arrays by add_size members and zero-init it. */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size = root->simple_rel_array_size + add_size; + + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + if (root->append_rel_array) + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + else + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + + /* Set the contents of just allocated memory to 0. */ + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + root->simple_rel_array_size = new_size; +} + /* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. @@ -238,7 +269,8 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte->relid, rte->inh, rte->updatedCols, + rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -279,52 +311,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); - /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. - */ - if (rte->inh) - { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); - - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; - } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); - } - return rel; } diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 0684083a8d..64787de16f 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -45,6 +45,7 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/planner.h" #include "optimizer/predtest.h" @@ -436,17 +437,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -540,23 +547,20 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ @@ -564,6 +568,13 @@ prune_append_rel_partitions(RelOptInfo *rel) return NULL; /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. */ @@ -590,15 +601,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index a2adbfc3b8..0303a454ce 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -15,6 +15,7 @@ #define RELATION_H #include "access/sdir.h" +#include "access/tupdesc.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/params.h" @@ -726,6 +727,7 @@ typedef struct RelOptInfo int nparts; /* number of partitions */ struct PartitionBoundInfoData *boundinfo; /* Partition bounds */ List *partition_qual; /* partition constraint */ + Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ List **partexprs; /* Non-nullable partition key expressions. */ @@ -735,6 +737,12 @@ typedef struct RelOptInfo Index inh_root_parent; /* For otherrels, this is the RT index of * inheritance table mentioned in the query * from which this relation originated */ + /* + * Set only if this is an inheritance parent relation. This information + * is needed when initializing the planning info for children. + */ + TupleDesc tupdesc; /* A "copy" of the table's tuple desriptor */ + Oid reltype; /* Table's reltype */ } RelOptInfo; /* diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index bd905d3328..e7d0a20c54 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -261,6 +261,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index a1b23251a1..ce666b56ad 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -26,7 +26,7 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); + bool inhparent, Bitmapset *updatedCols, RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index d6991d5690..9c0795262f 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -38,7 +38,8 @@ extern Expr *canonicalize_qual(Expr *qual, bool is_check); /* * prototypes for preptlist.c */ -extern List *preprocess_targetlist(PlannerInfo *root); +extern List *preprocess_targetlist(PlannerInfo *root, + bool inheritance_expanded); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); @@ -51,7 +52,10 @@ extern void expand_inherited_tables(PlannerInfo *root); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); - +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parentrel, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex); extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, Relids child_relids, Relids top_parent_relids); @@ -64,5 +68,4 @@ extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, Relids left_relids, Relids right_relids); extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, Relids child_relids, Relids top_parent_relids); - #endif /* PREP_H */ diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h index ce9bc8d9fd..5c5d27d040 100644 --- a/src/include/partitioning/partprune.h +++ b/src/include/partitioning/partprune.h @@ -76,7 +76,7 @@ extern PartitionPruneInfo *make_partition_pruneinfo(PlannerInfo *root, List *subpaths, List *partitioned_rels, List *prunequal); -extern Relids prune_append_rel_partitions(RelOptInfo *rel); +extern Bitmapset *prune_append_rel_partitions(RelOptInfo *rel); extern Bitmapset *get_matching_partitions(PartitionPruneContext *context, List *pruning_steps); diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 6bc106831e..1450cef057 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) -- 2.11.0
v11-0004-Move-inheritance-expansion-code-into-its-own-fil.patch (text/plain, 112.7K) download From 3b86331dd5a2368adc39c9fef92f3dd09d817a08 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 7 Nov 2018 16:51:31 +0900 Subject: [PATCH v11 4/6] Move inheritance expansion code into its own file This commit moves expand_inherited_tables and underlings currently in optimizer/prep/prepunionc.c to optimizer/utils/inherit.c. All of the AppendRelInfo based expression manipulation routines are moved to optimizer/utils/appendinfo.c. This commit only moves the code and contains no functional changes. --- src/backend/optimizer/path/allpaths.c | 2 + src/backend/optimizer/path/equivclass.c | 1 + src/backend/optimizer/path/joinrels.c | 1 + src/backend/optimizer/plan/planmain.c | 2 + src/backend/optimizer/plan/planner.c | 1 + src/backend/optimizer/prep/prepunion.c | 1573 ------------------------------- src/backend/optimizer/util/Makefile | 5 +- src/backend/optimizer/util/appendinfo.c | 851 +++++++++++++++++ src/backend/optimizer/util/inherit.c | 776 +++++++++++++++ src/backend/optimizer/util/pathnode.c | 1 + src/backend/optimizer/util/relnode.c | 1 + src/backend/partitioning/partprune.c | 1 + src/include/optimizer/appendinfo.h | 43 + src/include/optimizer/inherit.h | 25 + src/include/optimizer/prep.h | 19 - 15 files changed, 1708 insertions(+), 1594 deletions(-) create mode 100644 src/backend/optimizer/util/appendinfo.c create mode 100644 src/backend/optimizer/util/inherit.c create mode 100644 src/include/optimizer/appendinfo.h create mode 100644 src/include/optimizer/inherit.h diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index be7b8a717e..a3a686a153 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -30,6 +30,8 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "optimizer/inherit.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/geqo.h" diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 255e8f86a0..8b4ba1a828 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -22,6 +22,7 @@ #include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index be2e39fd4b..23335a1c2d 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -15,6 +15,7 @@ #include "postgres.h" #include "miscadmin.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 3f060927cc..825998ad51 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -20,7 +20,9 @@ */ #include "postgres.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" +#include "optimizer/inherit.h" #include "optimizer/orclauses.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index b9b56d9280..db277385d5 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -39,6 +39,7 @@ #include "nodes/print.h" #endif #include "nodes/relation.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 632781acc2..5d59385ef7 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -12,11 +12,6 @@ * case, but most of the heavy lifting for that is done elsewhere, * notably in prepjointree.c and allpaths.c. * - * There is also some code here to support planning of queries that use - * inheritance (SELECT FROM foo*). Inheritance trees are converted into - * append relations, and thenceforth share code with the UNION ALL case. - * - * * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -39,32 +34,21 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" -#include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" -#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" -#include "optimizer/var.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/selfuncs.h" #include "utils/syscache.h" -typedef struct -{ - PlannerInfo *root; - int nappinfos; - AppendRelInfo **appinfos; -} adjust_appendrel_attrs_context; - static RelOptInfo *recurse_set_operations(Node *setOp, PlannerInfo *root, List *colTypes, List *colCollations, bool junkOK, @@ -103,34 +87,6 @@ static List *generate_append_tlist(List *colTypes, List *colCollations, List *input_tlists, List *refnames_tlist); static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist); -static void expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, - RangeTblEntry *rte, Index rti); -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti, RelOptInfo *rel); -static void expand_partitioned_rtentry(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel); -static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel, - PlanRowMark *top_parentrc, Relation childrel, - RangeTblEntry **childrte_p, Index *childRTindex_p); -static void make_inh_translation_list(TupleDesc old_tupdesc, - TupleDesc new_tupdesc, - Oid from_rel, Oid to_rel, - Index newvarno, List **translated_vars); -static RelOptInfo *build_append_child_rel(PlannerInfo *root, - RelOptInfo *parent, - Index childRTindex); -static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); -static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars); -static Node *adjust_appendrel_attrs_mutator(Node *node, - adjust_appendrel_attrs_context *context); -static Relids adjust_child_relids(Relids relids, int nappinfos, - AppendRelInfo **appinfos); -static List *adjust_inherited_tlist(List *tlist, - AppendRelInfo *context); /* @@ -1467,1532 +1423,3 @@ generate_setop_grouplist(SetOperationStmt *op, List *targetlist) Assert(lg == NULL); return grouplist; } - - -/* - * expand_inherited_tables - * Expand each rangetable entry that represents an inheritance set - * into an "append relation". At the conclusion of this process, - * the "inh" flag is set in all and only those RTEs that are append - * relation parents. - */ -void -expand_inherited_tables(PlannerInfo *root) -{ - int orig_rtable_size; - Index rti; - - Assert(root->simple_rel_array_size > 0); - orig_rtable_size = root->simple_rel_array_size; - - /* - * expand_append_rtentry may add RTEs to parse->rtable. The function is - * expected to recursively handle any RTEs that it creates with inh=true. - * So just scan as far as the original end of the rtable list. - */ - for (rti = 1; rti < orig_rtable_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - RangeTblEntry *rte = root->simple_rte_array[rti]; - - /* there may be empty slots corresponding to non-baserel RTEs */ - if (brel == NULL) - continue; - - if (rte->inh) - expand_append_rtentry(root, brel, rte, rti); - } -} - -/* - * expand_append_rtentry - * This initializes RelOptInfos for an appendrel's child relations, if - * any - * - * 'rel' is the appendrel parent, whose range table entry ('rte') has been - * marked to require adding children. An appendrel parent could either - * be a subquery (if we flattened UNION ALL query) or a table that's known - * to have inheritance children. The latter consists of both regular - * inheritance parents and partitioned tables. - * - * For a subquery parent, there is not much to be done here because the - * children's RTEs are already present in the query, so we just initialize - * RelOptInfos for them. Also, the AppendRelInfos for child subqueries - * have already been added. - * - * For tables, we need to add the children to the range table and initialize - * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For - * a partitioned parent, we only add the children remaining after pruning. - * For regular inheritance parents, we find the children using - * find_all_inheritors and add all of them. - * - * If it turns out that there are no children, then we set rte->inh to false - * to let the caller know that only the parent table needs to be scanned. The - * caller can accordingly switch to a non-Append path. For a partitioned - * parent, that means an empty relation because parents themselves contain no - * data. - * - * For the regular inheritance case, the parent also gets another RTE with - * inh = false to represent it as an appendrel child. The original RTE is - * considered to represent the whole inheritance set. - */ -static void -expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, - Index rti) -{ - Assert(rte->inh); - /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ - Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); - - /* - * UNION ALL children already got RTEs and AppendRelInfos, so just build - * RelOptInfos and return. - * - * It might be a bit odd that this code is in this, because there is - * nothing to expand really. - */ - if (rte->rtekind == RTE_SUBQUERY) - { - ListCell *l; - - /* - * We don't need to use expand_planner_arrays in this case, because - * no new child RTEs are created. setup_simple_rel_arrays() and - * setup_append_rel_array would've considered these child RTEs when - * allocating space for various arrays. - */ - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst(l); - Index childRTindex = appinfo->child_relid; - - if (appinfo->parent_relid != rti) - continue; - - Assert(childRTindex < root->simple_rel_array_size); - Assert(root->simple_rte_array[childRTindex] != NULL); - - /* - * We set the correct value of baserestricinfo and - * baserestrict_min_security below. - */ - root->simple_rel_array[childRTindex] = - build_append_child_rel(root, rel, appinfo->child_relid); - } - } - else - { - Assert(rte->rtekind == RTE_RELATION); - Assert(has_subclass(rte->relid)); - - /* - * The rewriter should already have obtained an appropriate lock on - * each relation named in the query. However, for each child relation - * we add to the query, we must obtain an appropriate lock, because - * this will be the first use of those relations in the - * parse/rewrite/plan pipeline. Child rels should use the same - * lockmode as their parent. - */ - Assert(rte->rellockmode != NoLock); - - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - expand_partitioned_rtentry(root, rte, rti, rel); - else - expand_inherited_rtentry(root, rte, rti, rel); - } -} - -/* - * expand_inherited_rtentry - * Add entries for all the child tables to the query's rangetable, and - * build AppendRelInfo nodes for all the child tables and add them to - * root->append_rel_list. - * - * Note that the original RTE is considered to represent the whole - * inheritance set. The first of the generated RTEs is an RTE for the same - * table, but with inh = false, to represent the parent table in its role - * as a simple member of the inheritance set. - * - * A childless table is never considered to be an inheritance set. For - * regular inheritance, a parent RTE must always have at least two associated - * AppendRelInfos: one corresponding to the parent table as a simple member of - * inheritance set and one or more corresponding to the actual children. - */ -static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, - RelOptInfo *rel) -{ - Oid parentOID; - PlanRowMark *oldrc; - LOCKMODE lockmode = rte->rellockmode; - List *inhOIDs; - ListCell *l; - int num_children; - int num_children_added = 0; - - Assert(rte->rtekind == RTE_RELATION); - Assert(lockmode != NoLock); - parentOID = rte->relid; - - /* Scan for all members of inheritance set, acquire needed locks */ - inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); - - /* - * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite has_subclass() check performed by - * subquery_planner, if table once had a child but no longer does. - */ - num_children = list_length(inhOIDs); - if (num_children < 2) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks - * should've set isParent = true. We'll generate a new PlanRowMark for - * each child. - */ - oldrc = get_plan_rowmark(root->rowMarks, rti); - Assert(oldrc == NULL || oldrc->isParent); - - /* - * Must expand PlannerInfo arrays by num_children before we can add - * children. - */ - expand_planner_arrays(root, num_children); - - foreach(l, inhOIDs) - { - Oid childOID = lfirst_oid(l); - Relation newrelation; - RangeTblEntry *childrte; - Index childRTindex; - - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (RELATION_IS_OTHER_TEMP(newrelation)) - { - heap_close(newrelation, lockmode); - continue; - } - - (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, - newrelation, &childrte, - &childRTindex); - Assert(childrte != NULL); - /* All regular inheritance children are leaf children. */ - Assert(!childrte->inh); - Assert(childRTindex > 0); - - /* Close child relations, but keep locks */ - heap_close(newrelation, NoLock); - num_children_added++; - } - - /* - * If all children, including the parent (as child rel), were - * excluded, mark the parent rel as empty. If all the children were temp - * tables, pretend it's a non-inheritance situation; we don't need Append - * node in that case. The duplicate RTE we added for the parent table is - * harmless, so we don't bother to get rid of it; ditto for the useless - * PlanRowMark node. - */ - if (num_children_added == 0) - mark_dummy_rel(rel); - else if (num_children_added == 1) - rte->inh = false; - - /* - * Add junk columns needed by the row mark if any and also add the - * relevant expressions to the root parent's reltarget. - */ - if (oldrc) - { - List *tlist = add_rowmark_junk_columns(root, oldrc); - - build_base_rel_tlists(root, tlist); - } -} - -/* - * expand_partitioned_rtentry - * Prunes unnecessary partitions of a partitioned table and adds - * remaining ones to the Query and the PlannerInfo - * - * Partitions are added to the query in order in which they are found in - * the parent's PartitionDesc. - * - * Note: even though only the unpruned partitions will be added to the - * resulting plan, this still locks *all* partitions via find_all_inheritors - * when this function is called for the root partitioned table. - */ -static void -expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel) -{ - LOCKMODE lockmode = parentrte->rellockmode; - PlanRowMark *rootrc = NULL; - int i; - Bitmapset *partindexes; - Index rootParentRTindex = parentrel->inh_root_parent > 0 ? - parentrel->inh_root_parent : - parentRTindex; - - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - - /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, values of the - * indexes of partitioned relations that appear down in the tree will be - * bubbled up into root parent's list so that when we've created Paths for - * all the children, the root table's list will contain all such indexes. - */ - parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - - /* Perform pruning. */ - partindexes = prune_append_rel_partitions(parentrel); - - /* Must expand PlannerInfo arrays before we can add children. */ - expand_planner_arrays(root, bms_num_members(partindexes)); - - /* - * For partitioned tables, we also store the partition RelOptInfo - * pointers in the parent's RelOptInfo. - */ - parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * - parentrel->nparts); - - rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); - Assert(rootrc == NULL || rootrc->isParent); - i = -1; - while ((i = bms_next_member(partindexes, i)) >= 0) - { - Oid childOID = parentrel->part_oids[i]; - Relation newrelation; - RelOptInfo *childrel; - RangeTblEntry *childrte; - Index childRTindex; - - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); - Assert(!RELATION_IS_OTHER_TEMP(newrelation)); - - /* - * A partitioned child table with 0 children is a dummy rel, so don't - * bother creating planner objects for it. - */ - if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && - RelationGetPartitionDesc(newrelation)->nparts == 0) - { - heap_close(newrelation, NoLock); - continue; - } - - childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, - parentrel, rootrc, newrelation, - &childrte, &childRTindex); - Assert(childrel != NULL); - parentrel->part_rels[i] = childrel; - - /* Close child relations, but keep locks */ - heap_close(newrelation, NoLock); - - /* If the child is partitioned itself, expand it too. */ - if (childrel->part_scheme) - { - Assert(childrte->inh); - expand_partitioned_rtentry(root, childrte, childRTindex, - childrel); - } - } - - /* - * Add junk columns needed by the row mark if any and also add the - * relevant expressions to the root parent's reltarget. - */ - if (rootrc) - { - List *tlist = add_rowmark_junk_columns(root, rootrc); - - build_base_rel_tlists(root, tlist); - } -} - -/* - * add_inheritance_child_rel - * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally - * a RelOptInfo for an inheritance child relation. - * - * The return value is the RelOptInfo that's added. - * - * PlanRowMarks still carry the top-parent's RTI, and the top-parent's - * allMarkTypes field still accumulates values from all descendents. - * - * "parentrte" and "parentRTindex" are immediate parent's RTE and - * RTI. "top_parentrc" is top parent's PlanRowMark. - */ -static RelOptInfo * -add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, RelOptInfo *parentrel, - PlanRowMark *top_parentrc, Relation childrel, - RangeTblEntry **childrte_p, Index *childRTindex_p) -{ - Query *parse = root->parse; - Oid childOID = RelationGetRelid(childrel); - RangeTblEntry *childrte; - Index childRTindex; - AppendRelInfo *appinfo; - RelOptInfo *childrelopt; - - /* - * Build an RTE for the child, and attach to query's rangetable list. We - * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh appropriately. Also, set requiredPerms to zero - * since all required permissions checks are done on the original RTE. - * Likewise, set the child's securityQuals to empty, because we only want - * to apply the parent's RLS conditions regardless of what RLS properties - * individual children may have. (This is an intentional choice to make - * inherited RLS work like regular permissions checks.) The parent - * securityQuals will be propagated to children along with other base - * restriction clauses, so we don't need to do it here. - */ - childrte = copyObject(parentrte); - *childrte_p = childrte; - childrte->relid = childOID; - childrte->relkind = childrel->rd_rel->relkind; - /* - * A partitioned child will need to be expanded as an append parent - * itself, so set its inh to true. - */ - childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); - childrte->requiredPerms = 0; - childrte->securityQuals = NIL; - parse->rtable = lappend(parse->rtable, childrte); - childRTindex = list_length(parse->rtable); - *childRTindex_p = childRTindex; - - /* Create an AppendRelInfo and add it to planner's global list. */ - appinfo = make_append_rel_info(parentrel, parentrte, - childrel->rd_att, - childOID, - childrel->rd_rel->reltype, - childRTindex); - root->append_rel_list = lappend(root->append_rel_list, appinfo); - - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childrte->relid != parentrte->relid) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } - - /* - * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE. - */ - if (top_parentrc) - { - PlanRowMark *childrc = makeNode(PlanRowMark); - - childrc->rti = childRTindex; - childrc->prti = top_parentrc->rti; - childrc->rowmarkId = top_parentrc->rowmarkId; - /* Reselect rowmark type, because relkind might not match parent */ - childrc->markType = select_rowmark_type(childrte, - top_parentrc->strength); - childrc->allMarkTypes = (1 << childrc->markType); - childrc->strength = top_parentrc->strength; - childrc->waitPolicy = top_parentrc->waitPolicy; - - /* - * We mark RowMarks for partitioned child tables as parent RowMarks so - * that the executor ignores them (except their existence means that - * the child tables be locked using appropriate mode). - */ - childrc->isParent = (childrte->relkind == RELKIND_PARTITIONED_TABLE); - - /* Include child's rowmark type in top parent's allMarkTypes */ - top_parentrc->allMarkTypes |= childrc->allMarkTypes; - - root->rowMarks = lappend(root->rowMarks, childrc); - } - - /* - * Add the RelOptInfo. Even though we may not really scan this relation - * for reasons such as contradictory quals, we still need to create one, - * because for every RTE in the query's range table, there must be an - * accompanying RelOptInfo. - */ - - /* First, store the RTE and appinfos into planner arrays. */ - Assert(root->simple_rte_array[childRTindex] == NULL); - root->simple_rte_array[childRTindex] = childrte; - Assert(root->append_rel_array[childRTindex] == NULL); - root->append_rel_array[childRTindex] = appinfo; - - childrelopt = build_append_child_rel(root, parentrel, childRTindex); - Assert(childrelopt != NULL); - - return childrelopt; -} - -/* - * make_inh_translation_list - * Build the list of translations from parent Vars to child Vars for - * an inheritance child. - * - * For paranoia's sake, we match type/collation as well as attribute name. - */ -static void -make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, - Oid from_rel, Oid to_rel, - Index newvarno, List **translated_vars) -{ - List *vars = NIL; - int oldnatts = old_tupdesc->natts; - int newnatts = new_tupdesc->natts; - int old_attno; - int new_attno = 0; - - for (old_attno = 0; old_attno < oldnatts; old_attno++) - { - Form_pg_attribute att; - char *attname; - Oid atttypid; - int32 atttypmod; - Oid attcollation; - - att = TupleDescAttr(old_tupdesc, old_attno); - if (att->attisdropped) - { - /* Just put NULL into this list entry */ - vars = lappend(vars, NULL); - continue; - } - attname = NameStr(att->attname); - atttypid = att->atttypid; - atttypmod = att->atttypmod; - attcollation = att->attcollation; - - /* - * When we are generating the "translation list" for the parent table - * of an inheritance set, no need to search for matches. - */ - if (from_rel == to_rel) - { - vars = lappend(vars, makeVar(newvarno, - (AttrNumber) (old_attno + 1), - atttypid, - atttypmod, - attcollation, - 0)); - continue; - } - - /* - * Otherwise we have to search for the matching column by name. - * There's no guarantee it'll have the same column position, because - * of cases like ALTER TABLE ADD COLUMN and multiple inheritance. - * However, in simple cases, the relative order of columns is mostly - * the same in both relations, so try the column of newrelation that - * follows immediately after the one that we just found, and if that - * fails, let syscache handle it. - */ - if (new_attno >= newnatts || - (att = TupleDescAttr(new_tupdesc, new_attno))->attisdropped || - strcmp(attname, NameStr(att->attname)) != 0) - { - HeapTuple newtup; - - newtup = SearchSysCacheAttName(to_rel, attname); - if (!newtup) - elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, get_rel_name(to_rel)); - new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; - ReleaseSysCache(newtup); - - att = TupleDescAttr(new_tupdesc, new_attno); - } - - /* Found it, check type and collation match */ - if (atttypid != att->atttypid || atttypmod != att->atttypmod) - elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, get_rel_name(to_rel)); - if (attcollation != att->attcollation) - elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, get_rel_name(to_rel)); - - vars = lappend(vars, makeVar(newvarno, - (AttrNumber) (new_attno + 1), - atttypid, - atttypmod, - attcollation, - 0)); - new_attno++; - } - - *translated_vars = vars; -} - -/* - * translate_col_privs - * Translate a bitmapset representing per-column privileges from the - * parent rel's attribute numbering to the child's. - * - * The only surprise here is that we don't translate a parent whole-row - * reference into a child whole-row reference. That would mean requiring - * permissions on all child columns, which is overly strict, since the - * query is really only going to reference the inherited columns. Instead - * we set the per-column bits for all inherited columns. - */ -static Bitmapset * -translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars) -{ - Bitmapset *child_privs = NULL; - bool whole_row; - int attno; - ListCell *lc; - - /* System attributes have the same numbers in all tables */ - for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) - { - if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - attno - FirstLowInvalidHeapAttributeNumber); - } - - /* Check if parent has whole-row reference */ - whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, - parent_privs); - - /* And now translate the regular user attributes, using the vars list */ - attno = InvalidAttrNumber; - foreach(lc, translated_vars) - { - Var *var = lfirst_node(Var, lc); - - attno++; - if (var == NULL) /* ignore dropped columns */ - continue; - if (whole_row || - bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - var->varattno - FirstLowInvalidHeapAttributeNumber); - } - - return child_privs; -} - -/* - * build_append_child_rel - * Build a RelOptInfo for child relation of an append rel - * - * After creating the RelOptInfo for the given child RT index, it goes on to - * initialize some of its fields based on the parent RelOptInfo. - * - * If the quals in baserestrictinfo turn out to be self-contradictory, - * RelOptInfo is marked dummy before returning. - */ -static RelOptInfo * -build_append_child_rel(PlannerInfo *root, - RelOptInfo *parent, - Index childRTindex) -{ - RelOptInfo *childrel; - RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; - AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; - List *childquals; - ListCell *lc; - bool have_const_false_cq; - Index cq_min_security; - - /* Build the RelOptInfo. */ - childrel = build_simple_rel(root, childRTindex, parent); - - /* - * Propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - */ - childrel->direct_lateral_relids = parent->direct_lateral_relids; - childrel->lateral_relids = parent->lateral_relids; - childrel->lateral_referencers = parent->lateral_referencers; - - /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) - */ - childquals = false; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, parent->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual = (Node *) rinfo->clause; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, childqual, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might - * have securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals.) Pull any such securityQuals up into the - * baserestrictinfo for the child. This is similar to - * process_security_barrier_quals() for the parent rel, except - * that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* - * not likely that we'd see constants here, so no - * check - */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, - false, - false, - security_level, - NULL, NULL, - NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* Set child's version of baserestrictinfo. */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - } - - return childrel; -} - -/* - * add_rowmark_junk_columns - * Add necessary junk columns for rowmarked inheritance parent rel. - * - * These values are needed for locking of rels selected FOR UPDATE/SHARE, and - * to do EvalPlanQual rechecking. See comments for PlanRowMark in - * plannodes.h. - */ -static List * -add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) -{ - List *tlist = root->processed_tlist; - Var *var; - char resname[32]; - TargetEntry *tle; - - if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) - { - /* Need to fetch TID */ - var = makeVar(rc->rti, - SelfItemPointerAttributeNumber, - TIDOID, - -1, - InvalidOid, - 0); - snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - } - if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) - { - /* Need the whole row as a junk var */ - var = makeWholeRowVar(root->simple_rte_array[rc->rti], - rc->rti, - 0, - false); - snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - } - - /* For inheritance cases, always fetch the tableoid too. */ - var = makeVar(rc->rti, - TableOidAttributeNumber, - OIDOID, - -1, - InvalidOid, - 0); - snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - - return tlist; -} - -AppendRelInfo * -make_append_rel_info(RelOptInfo *parentrel, RangeTblEntry *parentrte, - TupleDesc childdesc, Oid childoid, Oid childreltype, - Index childRTindex) -{ - AppendRelInfo *appinfo = makeNode(AppendRelInfo); - - appinfo->parent_relid = parentrel->relid; - appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->reltype; - appinfo->child_reltype = childreltype; - make_inh_translation_list(parentrel->tupdesc, childdesc, - parentrte->relid, childoid, - childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = parentrte->relid; - - return appinfo; -} - -/* - * adjust_appendrel_attrs - * Copy the specified query or expression and translate Vars referring to a - * parent rel to refer to the corresponding child rel instead. We also - * update rtindexes appearing outside Vars, such as resultRelation and - * jointree relids. - * - * Note: this is only applied after conversion of sublinks to subplans, - * so we don't need to cope with recursion into sub-queries. - * - * Note: this is not hugely different from what pullup_replace_vars() does; - * maybe we should try to fold the two routines together. - */ -Node * -adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, - AppendRelInfo **appinfos) -{ - Node *result; - adjust_appendrel_attrs_context context; - - context.root = root; - context.nappinfos = nappinfos; - context.appinfos = appinfos; - - /* If there's nothing to adjust, don't call this function. */ - Assert(nappinfos >= 1 && appinfos != NULL); - - /* - * Must be prepared to start with a Query or a bare expression tree. - */ - if (node && IsA(node, Query)) - { - Query *newnode; - int cnt; - - newnode = query_tree_mutator((Query *) node, - adjust_appendrel_attrs_mutator, - (void *) &context, - QTW_IGNORE_RC_SUBQUERIES); - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (newnode->resultRelation == appinfo->parent_relid) - { - newnode->resultRelation = appinfo->child_relid; - /* Fix tlist resnos too, if it's inherited UPDATE */ - if (newnode->commandType == CMD_UPDATE) - newnode->targetList = - adjust_inherited_tlist(newnode->targetList, - appinfo); - break; - } - } - - result = (Node *) newnode; - } - else - result = adjust_appendrel_attrs_mutator(node, &context); - - return result; -} - -static Node * -adjust_appendrel_attrs_mutator(Node *node, - adjust_appendrel_attrs_context *context) -{ - AppendRelInfo **appinfos = context->appinfos; - int nappinfos = context->nappinfos; - int cnt; - - if (node == NULL) - return NULL; - if (IsA(node, Var)) - { - Var *var = (Var *) copyObject(node); - AppendRelInfo *appinfo = NULL; - - for (cnt = 0; cnt < nappinfos; cnt++) - { - if (var->varno == appinfos[cnt]->parent_relid) - { - appinfo = appinfos[cnt]; - break; - } - } - - if (var->varlevelsup == 0 && appinfo) - { - var->varno = appinfo->child_relid; - var->varnoold = appinfo->child_relid; - if (var->varattno > 0) - { - Node *newnode; - - if (var->varattno > list_length(appinfo->translated_vars)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - var->varattno, get_rel_name(appinfo->parent_reloid)); - newnode = copyObject(list_nth(appinfo->translated_vars, - var->varattno - 1)); - if (newnode == NULL) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - var->varattno, get_rel_name(appinfo->parent_reloid)); - return newnode; - } - else if (var->varattno == 0) - { - /* - * Whole-row Var: if we are dealing with named rowtypes, we - * can use a whole-row Var for the child table plus a coercion - * step to convert the tuple layout to the parent's rowtype. - * Otherwise we have to generate a RowExpr. - */ - if (OidIsValid(appinfo->child_reltype)) - { - Assert(var->vartype == appinfo->parent_reltype); - if (appinfo->parent_reltype != appinfo->child_reltype) - { - ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr); - - r->arg = (Expr *) var; - r->resulttype = appinfo->parent_reltype; - r->convertformat = COERCE_IMPLICIT_CAST; - r->location = -1; - /* Make sure the Var node has the right type ID, too */ - var->vartype = appinfo->child_reltype; - return (Node *) r; - } - } - else - { - /* - * Build a RowExpr containing the translated variables. - * - * In practice var->vartype will always be RECORDOID here, - * so we need to come up with some suitable column names. - * We use the parent RTE's column names. - * - * Note: we can't get here for inheritance cases, so there - * is no need to worry that translated_vars might contain - * some dummy NULLs. - */ - RowExpr *rowexpr; - List *fields; - RangeTblEntry *rte; - - rte = rt_fetch(appinfo->parent_relid, - context->root->parse->rtable); - fields = copyObject(appinfo->translated_vars); - rowexpr = makeNode(RowExpr); - rowexpr->args = fields; - rowexpr->row_typeid = var->vartype; - rowexpr->row_format = COERCE_IMPLICIT_CAST; - rowexpr->colnames = copyObject(rte->eref->colnames); - rowexpr->location = -1; - - return (Node *) rowexpr; - } - } - /* system attributes don't need any other translation */ - } - return (Node *) var; - } - if (IsA(node, CurrentOfExpr)) - { - CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node); - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (cexpr->cvarno == appinfo->parent_relid) - { - cexpr->cvarno = appinfo->child_relid; - break; - } - } - return (Node *) cexpr; - } - if (IsA(node, RangeTblRef)) - { - RangeTblRef *rtr = (RangeTblRef *) copyObject(node); - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (rtr->rtindex == appinfo->parent_relid) - { - rtr->rtindex = appinfo->child_relid; - break; - } - } - return (Node *) rtr; - } - if (IsA(node, JoinExpr)) - { - /* Copy the JoinExpr node with correct mutation of subnodes */ - JoinExpr *j; - AppendRelInfo *appinfo; - - j = (JoinExpr *) expression_tree_mutator(node, - adjust_appendrel_attrs_mutator, - (void *) context); - /* now fix JoinExpr's rtindex (probably never happens) */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - appinfo = appinfos[cnt]; - - if (j->rtindex == appinfo->parent_relid) - { - j->rtindex = appinfo->child_relid; - break; - } - } - return (Node *) j; - } - if (IsA(node, PlaceHolderVar)) - { - /* Copy the PlaceHolderVar node with correct mutation of subnodes */ - PlaceHolderVar *phv; - - phv = (PlaceHolderVar *) expression_tree_mutator(node, - adjust_appendrel_attrs_mutator, - (void *) context); - /* now fix PlaceHolderVar's relid sets */ - if (phv->phlevelsup == 0) - phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos, - context->appinfos); - return (Node *) phv; - } - - /* - * This is needed, because inheritance_make_rel_from_joinlist needs to - * translate root->join_info_list executing make_rel_from_joinlist for a - * given child. - */ - if (IsA(node, SpecialJoinInfo)) - { - SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; - SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); - - memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); - newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, - context->nappinfos, - context->appinfos); - newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, - context->nappinfos, - context->appinfos); - newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, - context->nappinfos, - context->appinfos); - newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, - context->nappinfos, - context->appinfos); - newinfo->semi_rhs_exprs = - (List *) expression_tree_mutator((Node *) - oldinfo->semi_rhs_exprs, - adjust_appendrel_attrs_mutator, - (void *) context); - return (Node *) newinfo; - } - - /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, AppendRelInfo)); - Assert(!IsA(node, PlaceHolderInfo)); - Assert(!IsA(node, MinMaxAggInfo)); - - /* - * We have to process RestrictInfo nodes specially. (Note: although - * set_append_rel_pathlist will hide RestrictInfos in the parent's - * baserestrictinfo list from us, it doesn't hide those in joininfo.) - */ - if (IsA(node, RestrictInfo)) - { - RestrictInfo *oldinfo = (RestrictInfo *) node; - RestrictInfo *newinfo = makeNode(RestrictInfo); - - /* Copy all flat-copiable fields */ - memcpy(newinfo, oldinfo, sizeof(RestrictInfo)); - - /* Recursively fix the clause itself */ - newinfo->clause = (Expr *) - adjust_appendrel_attrs_mutator((Node *) oldinfo->clause, context); - - /* and the modified version, if an OR clause */ - newinfo->orclause = (Expr *) - adjust_appendrel_attrs_mutator((Node *) oldinfo->orclause, context); - - /* adjust relid sets too */ - newinfo->clause_relids = adjust_child_relids(oldinfo->clause_relids, - context->nappinfos, - context->appinfos); - newinfo->required_relids = adjust_child_relids(oldinfo->required_relids, - context->nappinfos, - context->appinfos); - newinfo->outer_relids = adjust_child_relids(oldinfo->outer_relids, - context->nappinfos, - context->appinfos); - newinfo->nullable_relids = adjust_child_relids(oldinfo->nullable_relids, - context->nappinfos, - context->appinfos); - newinfo->left_relids = adjust_child_relids(oldinfo->left_relids, - context->nappinfos, - context->appinfos); - newinfo->right_relids = adjust_child_relids(oldinfo->right_relids, - context->nappinfos, - context->appinfos); - - /* - * Reset cached derivative fields, since these might need to have - * different values when considering the child relation. Note we - * don't reset left_ec/right_ec: each child variable is implicitly - * equivalent to its parent, so still a member of the same EC if any. - */ - newinfo->eval_cost.startup = -1; - newinfo->norm_selec = -1; - newinfo->outer_selec = -1; - newinfo->left_em = NULL; - newinfo->right_em = NULL; - newinfo->scansel_cache = NIL; - newinfo->left_bucketsize = -1; - newinfo->right_bucketsize = -1; - newinfo->left_mcvfreq = -1; - newinfo->right_mcvfreq = -1; - - return (Node *) newinfo; - } - - /* - * NOTE: we do not need to recurse into sublinks, because they should - * already have been converted to subplans before we see them. - */ - Assert(!IsA(node, SubLink)); - Assert(!IsA(node, Query)); - - return expression_tree_mutator(node, adjust_appendrel_attrs_mutator, - (void *) context); -} - -/* - * Substitute child relids for parent relids in a Relid set. The array of - * appinfos specifies the substitutions to be performed. - */ -static Relids -adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos) -{ - Bitmapset *result = NULL; - int cnt; - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - /* Remove parent, add child */ - if (bms_is_member(appinfo->parent_relid, relids)) - { - /* Make a copy if we are changing the set. */ - if (!result) - result = bms_copy(relids); - - result = bms_del_member(result, appinfo->parent_relid); - result = bms_add_member(result, appinfo->child_relid); - } - } - - /* If we made any changes, return the modified copy. */ - if (result) - return result; - - /* Otherwise, return the original set without modification. */ - return relids; -} - -/* - * Replace any relid present in top_parent_relids with its child in - * child_relids. Members of child_relids can be multiple levels below top - * parent in the partition hierarchy. - */ -Relids -adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, - Relids child_relids, Relids top_parent_relids) -{ - AppendRelInfo **appinfos; - int nappinfos; - Relids parent_relids = NULL; - Relids result; - Relids tmp_result = NULL; - int cnt; - - /* - * If the given relids set doesn't contain any of the top parent relids, - * it will remain unchanged. - */ - if (!bms_overlap(relids, top_parent_relids)) - return relids; - - appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); - - /* Construct relids set for the immediate parent of the given child. */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); - } - - /* Recurse if immediate parent is not the top parent. */ - if (!bms_equal(parent_relids, top_parent_relids)) - { - tmp_result = adjust_child_relids_multilevel(root, relids, - parent_relids, - top_parent_relids); - relids = tmp_result; - } - - result = adjust_child_relids(relids, nappinfos, appinfos); - - /* Free memory consumed by any intermediate result. */ - if (tmp_result) - bms_free(tmp_result); - bms_free(parent_relids); - pfree(appinfos); - - return result; -} - -/* - * Adjust the targetlist entries of an inherited UPDATE operation - * - * The expressions have already been fixed, but we have to make sure that - * the target resnos match the child table (they may not, in the case of - * a column that was added after-the-fact by ALTER TABLE). In some cases - * this can force us to re-order the tlist to preserve resno ordering. - * (We do all this work in special cases so that preptlist.c is fast for - * the typical case.) - * - * The given tlist has already been through expression_tree_mutator; - * therefore the TargetEntry nodes are fresh copies that it's okay to - * scribble on. - * - * Note that this is not needed for INSERT because INSERT isn't inheritable. - */ -static List * -adjust_inherited_tlist(List *tlist, AppendRelInfo *context) -{ - bool changed_it = false; - ListCell *tl; - List *new_tlist; - bool more; - int attrno; - - /* This should only happen for an inheritance case, not UNION ALL */ - Assert(OidIsValid(context->parent_reloid)); - - /* Scan tlist and update resnos to match attnums of child rel */ - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - Var *childvar; - - if (tle->resjunk) - continue; /* ignore junk items */ - - /* Look up the translation of this column: it must be a Var */ - if (tle->resno <= 0 || - tle->resno > list_length(context->translated_vars)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - tle->resno, get_rel_name(context->parent_reloid)); - childvar = (Var *) list_nth(context->translated_vars, tle->resno - 1); - if (childvar == NULL || !IsA(childvar, Var)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - tle->resno, get_rel_name(context->parent_reloid)); - - if (tle->resno != childvar->varattno) - { - tle->resno = childvar->varattno; - changed_it = true; - } - } - - /* - * If we changed anything, re-sort the tlist by resno, and make sure - * resjunk entries have resnos above the last real resno. The sort - * algorithm is a bit stupid, but for such a seldom-taken path, small is - * probably better than fast. - */ - if (!changed_it) - return tlist; - - new_tlist = NIL; - more = true; - for (attrno = 1; more; attrno++) - { - more = false; - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - - if (tle->resjunk) - continue; /* ignore junk items */ - - if (tle->resno == attrno) - new_tlist = lappend(new_tlist, tle); - else if (tle->resno > attrno) - more = true; - } - } - - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - - if (!tle->resjunk) - continue; /* here, ignore non-junk items */ - - tle->resno = attrno; - new_tlist = lappend(new_tlist, tle); - attrno++; - } - - return new_tlist; -} - -/* - * adjust_appendrel_attrs_multilevel - * Apply Var translations from a toplevel appendrel parent down to a child. - * - * In some cases we need to translate expressions referencing a parent relation - * to reference an appendrel child that's multiple levels removed from it. - */ -Node * -adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, - Relids child_relids, - Relids top_parent_relids) -{ - AppendRelInfo **appinfos; - Bitmapset *parent_relids = NULL; - int nappinfos; - int cnt; - - Assert(bms_num_members(child_relids) == bms_num_members(top_parent_relids)); - - appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); - - /* Construct relids set for the immediate parent of given child. */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); - } - - /* Recurse if immediate parent is not the top parent. */ - if (!bms_equal(parent_relids, top_parent_relids)) - node = adjust_appendrel_attrs_multilevel(root, node, parent_relids, - top_parent_relids); - - /* Now translate for this child */ - node = adjust_appendrel_attrs(root, node, nappinfos, appinfos); - - pfree(appinfos); - - return node; -} - -/* - * Construct the SpecialJoinInfo for a child-join by translating - * SpecialJoinInfo for the join between parents. left_relids and right_relids - * are the relids of left and right side of the join respectively. - */ -SpecialJoinInfo * -build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, - Relids left_relids, Relids right_relids) -{ - SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo); - AppendRelInfo **left_appinfos; - int left_nappinfos; - AppendRelInfo **right_appinfos; - int right_nappinfos; - - memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo)); - left_appinfos = find_appinfos_by_relids(root, left_relids, - &left_nappinfos); - right_appinfos = find_appinfos_by_relids(root, right_relids, - &right_nappinfos); - - sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand, - left_nappinfos, left_appinfos); - sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand, - right_nappinfos, - right_appinfos); - sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand, - left_nappinfos, left_appinfos); - sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand, - right_nappinfos, - right_appinfos); - sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root, - (Node *) sjinfo->semi_rhs_exprs, - right_nappinfos, - right_appinfos); - - pfree(left_appinfos); - pfree(right_appinfos); - - return sjinfo; -} - -/* - * find_appinfos_by_relids - * Find AppendRelInfo structures for all relations specified by relids. - * - * The AppendRelInfos are returned in an array, which can be pfree'd by the - * caller. *nappinfos is set to the number of entries in the array. - */ -AppendRelInfo ** -find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos) -{ - AppendRelInfo **appinfos; - int cnt = 0; - int i; - - *nappinfos = bms_num_members(relids); - appinfos = (AppendRelInfo **) palloc(sizeof(AppendRelInfo *) * *nappinfos); - - i = -1; - while ((i = bms_next_member(relids, i)) >= 0) - { - AppendRelInfo *appinfo = root->append_rel_array[i]; - - if (!appinfo) - elog(ERROR, "child rel %d not found in append_rel_array", i); - - appinfos[cnt++] = appinfo; - } - return appinfos; -} diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile index da9ccf32b4..8a5e2ad1bd 100644 --- a/src/backend/optimizer/util/Makefile +++ b/src/backend/optimizer/util/Makefile @@ -12,7 +12,8 @@ subdir = src/backend/optimizer/util top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = clauses.o joininfo.o orclauses.o pathnode.o placeholder.o \ - plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o +OBJS = appendinfo.o clauses.o inherit.o joininfo.o orclauses.o pathnode.o \ + placeholder.o plancat.o predtest.o relnode.o restrictinfo.o tlist.o \ + var.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c new file mode 100644 index 0000000000..7d3937b3ce --- /dev/null +++ b/src/backend/optimizer/util/appendinfo.c @@ -0,0 +1,851 @@ +/*------------------------------------------------------------------------- + * + * appendinfo.c + * Routines for mapping between append parent(s) and children + * + * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/optimizer/path/appendinfo.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" +#include "parser/parsetree.h" +#include "utils/rel.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" + +typedef struct +{ + PlannerInfo *root; + int nappinfos; + AppendRelInfo **appinfos; +} adjust_appendrel_attrs_context; + +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); +static Node *adjust_appendrel_attrs_mutator(Node *node, + adjust_appendrel_attrs_context *context); +static Relids adjust_child_relids(Relids relids, int nappinfos, + AppendRelInfo **appinfos); +static List *adjust_inherited_tlist(List *tlist, + AppendRelInfo *context); + +AppendRelInfo * +make_append_rel_info(RelOptInfo *parentrel, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex) +{ + AppendRelInfo *appinfo = makeNode(AppendRelInfo); + + appinfo->parent_relid = parentrel->relid; + appinfo->child_relid = childRTindex; + appinfo->parent_reltype = parentrel->reltype; + appinfo->child_reltype = childreltype; + make_inh_translation_list(parentrel->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, + &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; + + return appinfo; +} + +/* + * adjust_appendrel_attrs + * Copy the specified query or expression and translate Vars referring to a + * parent rel to refer to the corresponding child rel instead. We also + * update rtindexes appearing outside Vars, such as resultRelation and + * jointree relids. + * + * Note: this is only applied after conversion of sublinks to subplans, + * so we don't need to cope with recursion into sub-queries. + * + * Note: this is not hugely different from what pullup_replace_vars() does; + * maybe we should try to fold the two routines together. + */ +Node * +adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, + AppendRelInfo **appinfos) +{ + Node *result; + adjust_appendrel_attrs_context context; + + context.root = root; + context.nappinfos = nappinfos; + context.appinfos = appinfos; + + /* If there's nothing to adjust, don't call this function. */ + Assert(nappinfos >= 1 && appinfos != NULL); + + /* + * Must be prepared to start with a Query or a bare expression tree. + */ + if (node && IsA(node, Query)) + { + Query *newnode; + int cnt; + + newnode = query_tree_mutator((Query *) node, + adjust_appendrel_attrs_mutator, + (void *) &context, + QTW_IGNORE_RC_SUBQUERIES); + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (newnode->resultRelation == appinfo->parent_relid) + { + newnode->resultRelation = appinfo->child_relid; + /* Fix tlist resnos too, if it's inherited UPDATE */ + if (newnode->commandType == CMD_UPDATE) + newnode->targetList = + adjust_inherited_tlist(newnode->targetList, + appinfo); + break; + } + } + + result = (Node *) newnode; + } + else + result = adjust_appendrel_attrs_mutator(node, &context); + + return result; +} + +static Node * +adjust_appendrel_attrs_mutator(Node *node, + adjust_appendrel_attrs_context *context) +{ + AppendRelInfo **appinfos = context->appinfos; + int nappinfos = context->nappinfos; + int cnt; + + if (node == NULL) + return NULL; + if (IsA(node, Var)) + { + Var *var = (Var *) copyObject(node); + AppendRelInfo *appinfo = NULL; + + for (cnt = 0; cnt < nappinfos; cnt++) + { + if (var->varno == appinfos[cnt]->parent_relid) + { + appinfo = appinfos[cnt]; + break; + } + } + + if (var->varlevelsup == 0 && appinfo) + { + var->varno = appinfo->child_relid; + var->varnoold = appinfo->child_relid; + if (var->varattno > 0) + { + Node *newnode; + + if (var->varattno > list_length(appinfo->translated_vars)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + var->varattno, get_rel_name(appinfo->parent_reloid)); + newnode = copyObject(list_nth(appinfo->translated_vars, + var->varattno - 1)); + if (newnode == NULL) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + var->varattno, get_rel_name(appinfo->parent_reloid)); + return newnode; + } + else if (var->varattno == 0) + { + /* + * Whole-row Var: if we are dealing with named rowtypes, we + * can use a whole-row Var for the child table plus a coercion + * step to convert the tuple layout to the parent's rowtype. + * Otherwise we have to generate a RowExpr. + */ + if (OidIsValid(appinfo->child_reltype)) + { + Assert(var->vartype == appinfo->parent_reltype); + if (appinfo->parent_reltype != appinfo->child_reltype) + { + ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr); + + r->arg = (Expr *) var; + r->resulttype = appinfo->parent_reltype; + r->convertformat = COERCE_IMPLICIT_CAST; + r->location = -1; + /* Make sure the Var node has the right type ID, too */ + var->vartype = appinfo->child_reltype; + return (Node *) r; + } + } + else + { + /* + * Build a RowExpr containing the translated variables. + * + * In practice var->vartype will always be RECORDOID here, + * so we need to come up with some suitable column names. + * We use the parent RTE's column names. + * + * Note: we can't get here for inheritance cases, so there + * is no need to worry that translated_vars might contain + * some dummy NULLs. + */ + RowExpr *rowexpr; + List *fields; + RangeTblEntry *rte; + + rte = rt_fetch(appinfo->parent_relid, + context->root->parse->rtable); + fields = copyObject(appinfo->translated_vars); + rowexpr = makeNode(RowExpr); + rowexpr->args = fields; + rowexpr->row_typeid = var->vartype; + rowexpr->row_format = COERCE_IMPLICIT_CAST; + rowexpr->colnames = copyObject(rte->eref->colnames); + rowexpr->location = -1; + + return (Node *) rowexpr; + } + } + /* system attributes don't need any other translation */ + } + return (Node *) var; + } + if (IsA(node, CurrentOfExpr)) + { + CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node); + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (cexpr->cvarno == appinfo->parent_relid) + { + cexpr->cvarno = appinfo->child_relid; + break; + } + } + return (Node *) cexpr; + } + if (IsA(node, RangeTblRef)) + { + RangeTblRef *rtr = (RangeTblRef *) copyObject(node); + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (rtr->rtindex == appinfo->parent_relid) + { + rtr->rtindex = appinfo->child_relid; + break; + } + } + return (Node *) rtr; + } + if (IsA(node, JoinExpr)) + { + /* Copy the JoinExpr node with correct mutation of subnodes */ + JoinExpr *j; + AppendRelInfo *appinfo; + + j = (JoinExpr *) expression_tree_mutator(node, + adjust_appendrel_attrs_mutator, + (void *) context); + /* now fix JoinExpr's rtindex (probably never happens) */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + appinfo = appinfos[cnt]; + + if (j->rtindex == appinfo->parent_relid) + { + j->rtindex = appinfo->child_relid; + break; + } + } + return (Node *) j; + } + if (IsA(node, PlaceHolderVar)) + { + /* Copy the PlaceHolderVar node with correct mutation of subnodes */ + PlaceHolderVar *phv; + + phv = (PlaceHolderVar *) expression_tree_mutator(node, + adjust_appendrel_attrs_mutator, + (void *) context); + /* now fix PlaceHolderVar's relid sets */ + if (phv->phlevelsup == 0) + phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos, + context->appinfos); + return (Node *) phv; + } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + + /* Shouldn't need to handle planner auxiliary nodes here */ + Assert(!IsA(node, AppendRelInfo)); + Assert(!IsA(node, PlaceHolderInfo)); + Assert(!IsA(node, MinMaxAggInfo)); + + /* + * We have to process RestrictInfo nodes specially. (Note: although + * set_append_rel_pathlist will hide RestrictInfos in the parent's + * baserestrictinfo list from us, it doesn't hide those in joininfo.) + */ + if (IsA(node, RestrictInfo)) + { + RestrictInfo *oldinfo = (RestrictInfo *) node; + RestrictInfo *newinfo = makeNode(RestrictInfo); + + /* Copy all flat-copiable fields */ + memcpy(newinfo, oldinfo, sizeof(RestrictInfo)); + + /* Recursively fix the clause itself */ + newinfo->clause = (Expr *) + adjust_appendrel_attrs_mutator((Node *) oldinfo->clause, context); + + /* and the modified version, if an OR clause */ + newinfo->orclause = (Expr *) + adjust_appendrel_attrs_mutator((Node *) oldinfo->orclause, context); + + /* adjust relid sets too */ + newinfo->clause_relids = adjust_child_relids(oldinfo->clause_relids, + context->nappinfos, + context->appinfos); + newinfo->required_relids = adjust_child_relids(oldinfo->required_relids, + context->nappinfos, + context->appinfos); + newinfo->outer_relids = adjust_child_relids(oldinfo->outer_relids, + context->nappinfos, + context->appinfos); + newinfo->nullable_relids = adjust_child_relids(oldinfo->nullable_relids, + context->nappinfos, + context->appinfos); + newinfo->left_relids = adjust_child_relids(oldinfo->left_relids, + context->nappinfos, + context->appinfos); + newinfo->right_relids = adjust_child_relids(oldinfo->right_relids, + context->nappinfos, + context->appinfos); + + /* + * Reset cached derivative fields, since these might need to have + * different values when considering the child relation. Note we + * don't reset left_ec/right_ec: each child variable is implicitly + * equivalent to its parent, so still a member of the same EC if any. + */ + newinfo->eval_cost.startup = -1; + newinfo->norm_selec = -1; + newinfo->outer_selec = -1; + newinfo->left_em = NULL; + newinfo->right_em = NULL; + newinfo->scansel_cache = NIL; + newinfo->left_bucketsize = -1; + newinfo->right_bucketsize = -1; + newinfo->left_mcvfreq = -1; + newinfo->right_mcvfreq = -1; + + return (Node *) newinfo; + } + + /* + * NOTE: we do not need to recurse into sublinks, because they should + * already have been converted to subplans before we see them. + */ + Assert(!IsA(node, SubLink)); + Assert(!IsA(node, Query)); + + return expression_tree_mutator(node, adjust_appendrel_attrs_mutator, + (void *) context); +} + +/* + * Substitute child relids for parent relids in a Relid set. The array of + * appinfos specifies the substitutions to be performed. + */ +static Relids +adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos) +{ + Bitmapset *result = NULL; + int cnt; + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + /* Remove parent, add child */ + if (bms_is_member(appinfo->parent_relid, relids)) + { + /* Make a copy if we are changing the set. */ + if (!result) + result = bms_copy(relids); + + result = bms_del_member(result, appinfo->parent_relid); + result = bms_add_member(result, appinfo->child_relid); + } + } + + /* If we made any changes, return the modified copy. */ + if (result) + return result; + + /* Otherwise, return the original set without modification. */ + return relids; +} + +/* + * Replace any relid present in top_parent_relids with its child in + * child_relids. Members of child_relids can be multiple levels below top + * parent in the partition hierarchy. + */ +Relids +adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, + Relids child_relids, Relids top_parent_relids) +{ + AppendRelInfo **appinfos; + int nappinfos; + Relids parent_relids = NULL; + Relids result; + Relids tmp_result = NULL; + int cnt; + + /* + * If the given relids set doesn't contain any of the top parent relids, + * it will remain unchanged. + */ + if (!bms_overlap(relids, top_parent_relids)) + return relids; + + appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); + + /* Construct relids set for the immediate parent of the given child. */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); + } + + /* Recurse if immediate parent is not the top parent. */ + if (!bms_equal(parent_relids, top_parent_relids)) + { + tmp_result = adjust_child_relids_multilevel(root, relids, + parent_relids, + top_parent_relids); + relids = tmp_result; + } + + result = adjust_child_relids(relids, nappinfos, appinfos); + + /* Free memory consumed by any intermediate result. */ + if (tmp_result) + bms_free(tmp_result); + bms_free(parent_relids); + pfree(appinfos); + + return result; +} + +/* + * adjust_appendrel_attrs_multilevel + * Apply Var translations from a toplevel appendrel parent down to a child. + * + * In some cases we need to translate expressions referencing a parent relation + * to reference an appendrel child that's multiple levels removed from it. + */ +Node * +adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, + Relids child_relids, + Relids top_parent_relids) +{ + AppendRelInfo **appinfos; + Bitmapset *parent_relids = NULL; + int nappinfos; + int cnt; + + Assert(bms_num_members(child_relids) == bms_num_members(top_parent_relids)); + + appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); + + /* Construct relids set for the immediate parent of given child. */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); + } + + /* Recurse if immediate parent is not the top parent. */ + if (!bms_equal(parent_relids, top_parent_relids)) + node = adjust_appendrel_attrs_multilevel(root, node, parent_relids, + top_parent_relids); + + /* Now translate for this child */ + node = adjust_appendrel_attrs(root, node, nappinfos, appinfos); + + pfree(appinfos); + + return node; +} + +/* + * Construct the SpecialJoinInfo for a child-join by translating + * SpecialJoinInfo for the join between parents. left_relids and right_relids + * are the relids of left and right side of the join respectively. + */ +SpecialJoinInfo * +build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, + Relids left_relids, Relids right_relids) +{ + SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo); + AppendRelInfo **left_appinfos; + int left_nappinfos; + AppendRelInfo **right_appinfos; + int right_nappinfos; + + memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo)); + left_appinfos = find_appinfos_by_relids(root, left_relids, + &left_nappinfos); + right_appinfos = find_appinfos_by_relids(root, right_relids, + &right_nappinfos); + + sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand, + left_nappinfos, left_appinfos); + sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand, + right_nappinfos, + right_appinfos); + sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand, + left_nappinfos, left_appinfos); + sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand, + right_nappinfos, + right_appinfos); + sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root, + (Node *) sjinfo->semi_rhs_exprs, + right_nappinfos, + right_appinfos); + + pfree(left_appinfos); + pfree(right_appinfos); + + return sjinfo; +} + +/* + * find_appinfos_by_relids + * Find AppendRelInfo structures for all relations specified by relids. + * + * The AppendRelInfos are returned in an array, which can be pfree'd by the + * caller. *nappinfos is set to the number of entries in the array. + */ +AppendRelInfo ** +find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos) +{ + AppendRelInfo **appinfos; + int cnt = 0; + int i; + + *nappinfos = bms_num_members(relids); + appinfos = (AppendRelInfo **) palloc(sizeof(AppendRelInfo *) * *nappinfos); + + i = -1; + while ((i = bms_next_member(relids, i)) >= 0) + { + AppendRelInfo *appinfo = root->append_rel_array[i]; + + if (!appinfo) + elog(ERROR, "child rel %d not found in append_rel_array", i); + + appinfos[cnt++] = appinfo; + } + return appinfos; +} + +/* + * make_inh_translation_list + * Build the list of translations from parent Vars to child Vars for + * an inheritance child. + * + * For paranoia's sake, we match type/collation as well as attribute name. + */ +static void +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) +{ + List *vars = NIL; + int oldnatts = old_tupdesc->natts; + int newnatts = new_tupdesc->natts; + int old_attno; + int new_attno = 0; + + for (old_attno = 0; old_attno < oldnatts; old_attno++) + { + Form_pg_attribute att; + char *attname; + Oid atttypid; + int32 atttypmod; + Oid attcollation; + + att = TupleDescAttr(old_tupdesc, old_attno); + if (att->attisdropped) + { + /* Just put NULL into this list entry */ + vars = lappend(vars, NULL); + continue; + } + attname = NameStr(att->attname); + atttypid = att->atttypid; + atttypmod = att->atttypmod; + attcollation = att->attcollation; + + /* + * When we are generating the "translation list" for the parent table + * of an inheritance set, no need to search for matches. + */ + if (from_rel == to_rel) + { + vars = lappend(vars, makeVar(newvarno, + (AttrNumber) (old_attno + 1), + atttypid, + atttypmod, + attcollation, + 0)); + continue; + } + + /* + * Otherwise we have to search for the matching column by name. + * There's no guarantee it'll have the same column position, because + * of cases like ALTER TABLE ADD COLUMN and multiple inheritance. + * However, in simple cases, the relative order of columns is mostly + * the same in both relations, so try the column of newrelation that + * follows immediately after the one that we just found, and if that + * fails, let syscache handle it. + */ + if (new_attno >= newnatts || + (att = TupleDescAttr(new_tupdesc, new_attno))->attisdropped || + strcmp(attname, NameStr(att->attname)) != 0) + { + HeapTuple newtup; + + newtup = SearchSysCacheAttName(to_rel, attname); + if (!newtup) + elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", + attname, get_rel_name(to_rel)); + new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; + ReleaseSysCache(newtup); + + att = TupleDescAttr(new_tupdesc, new_attno); + } + + /* Found it, check type and collation match */ + if (atttypid != att->atttypid || atttypmod != att->atttypmod) + elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", + attname, get_rel_name(to_rel)); + if (attcollation != att->attcollation) + elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", + attname, get_rel_name(to_rel)); + + vars = lappend(vars, makeVar(newvarno, + (AttrNumber) (new_attno + 1), + atttypid, + atttypmod, + attcollation, + 0)); + new_attno++; + } + + *translated_vars = vars; +} + +/* + * translate_col_privs + * Translate a bitmapset representing per-column privileges from the + * parent rel's attribute numbering to the child's. + * + * The only surprise here is that we don't translate a parent whole-row + * reference into a child whole-row reference. That would mean requiring + * permissions on all child columns, which is overly strict, since the + * query is really only going to reference the inherited columns. Instead + * we set the per-column bits for all inherited columns. + */ +Bitmapset * +translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars) +{ + Bitmapset *child_privs = NULL; + bool whole_row; + int attno; + ListCell *lc; + + /* System attributes have the same numbers in all tables */ + for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) + { + if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + attno - FirstLowInvalidHeapAttributeNumber); + } + + /* Check if parent has whole-row reference */ + whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, + parent_privs); + + /* And now translate the regular user attributes, using the vars list */ + attno = InvalidAttrNumber; + foreach(lc, translated_vars) + { + Var *var = lfirst_node(Var, lc); + + attno++; + if (var == NULL) /* ignore dropped columns */ + continue; + if (whole_row || + bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + var->varattno - FirstLowInvalidHeapAttributeNumber); + } + + return child_privs; +} + +/* + * Adjust the targetlist entries of an inherited UPDATE operation + * + * The expressions have already been fixed, but we have to make sure that + * the target resnos match the child table (they may not, in the case of + * a column that was added after-the-fact by ALTER TABLE). In some cases + * this can force us to re-order the tlist to preserve resno ordering. + * (We do all this work in special cases so that preptlist.c is fast for + * the typical case.) + * + * The given tlist has already been through expression_tree_mutator; + * therefore the TargetEntry nodes are fresh copies that it's okay to + * scribble on. + * + * Note that this is not needed for INSERT because INSERT isn't inheritable. + */ +static List * +adjust_inherited_tlist(List *tlist, AppendRelInfo *context) +{ + bool changed_it = false; + ListCell *tl; + List *new_tlist; + bool more; + int attrno; + + /* This should only happen for an inheritance case, not UNION ALL */ + Assert(OidIsValid(context->parent_reloid)); + + /* Scan tlist and update resnos to match attnums of child rel */ + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + Var *childvar; + + if (tle->resjunk) + continue; /* ignore junk items */ + + /* Look up the translation of this column: it must be a Var */ + if (tle->resno <= 0 || + tle->resno > list_length(context->translated_vars)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + tle->resno, get_rel_name(context->parent_reloid)); + childvar = (Var *) list_nth(context->translated_vars, tle->resno - 1); + if (childvar == NULL || !IsA(childvar, Var)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + tle->resno, get_rel_name(context->parent_reloid)); + + if (tle->resno != childvar->varattno) + { + tle->resno = childvar->varattno; + changed_it = true; + } + } + + /* + * If we changed anything, re-sort the tlist by resno, and make sure + * resjunk entries have resnos above the last real resno. The sort + * algorithm is a bit stupid, but for such a seldom-taken path, small is + * probably better than fast. + */ + if (!changed_it) + return tlist; + + new_tlist = NIL; + more = true; + for (attrno = 1; more; attrno++) + { + more = false; + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + + if (tle->resjunk) + continue; /* ignore junk items */ + + if (tle->resno == attrno) + new_tlist = lappend(new_tlist, tle); + else if (tle->resno > attrno) + more = true; + } + } + + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + + if (!tle->resjunk) + continue; /* here, ignore non-junk items */ + + tle->resno = attrno; + new_tlist = lappend(new_tlist, tle); + attrno++; + } + + return new_tlist; +} diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c new file mode 100644 index 0000000000..2224c5911f --- /dev/null +++ b/src/backend/optimizer/util/inherit.c @@ -0,0 +1,776 @@ +/*------------------------------------------------------------------------- + * + * inherit.c + * Routines to process child relations in inheritance trees + * + * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/optimizer/path/inherit.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <limits.h> + +#include "access/sysattr.h" +#include "catalog/partition.h" +#include "catalog/pg_class.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_type.h" +#include "nodes/makefuncs.h" +#include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" +#include "optimizer/inherit.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" +#include "optimizer/plancat.h" +#include "optimizer/planmain.h" +#include "optimizer/planner.h" +#include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" +#include "optimizer/tlist.h" +#include "optimizer/var.h" +#include "partitioning/partprune.h" +#include "utils/rel.h" + +static void expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti); +static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, + Index rti, RelOptInfo *rel); +static void expand_partitioned_rtentry(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p); +static RelOptInfo *build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); + + +/* + * expand_inherited_tables + * Expand each rangetable entry that represents an inheritance set + * into an "append relation". At the conclusion of this process, + * the "inh" flag is set in all and only those RTEs that are append + * relation parents. + */ +void +expand_inherited_tables(PlannerInfo *root) +{ + int orig_rtable_size; + Index rti; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; + + /* + * expand_append_rtentry may add RTEs to parse->rtable. The function is + * expected to recursively handle any RTEs that it creates with inh=true. + * So just scan as far as the original end of the rtable list. + */ + for (rti = 1; rti < orig_rtable_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; + + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + if (rte->inh) + expand_append_rtentry(root, brel, rte, rti); + } +} + +/* + * expand_append_rtentry + * This initializes RelOptInfos for an appendrel's child relations, if + * any + * + * 'rel' is the appendrel parent, whose range table entry ('rte') has been + * marked to require adding children. An appendrel parent could either + * be a subquery (if we flattened UNION ALL query) or a table that's known + * to have inheritance children. The latter consists of both regular + * inheritance parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as an appendrel child. The original RTE is + * considered to represent the whole inheritance set. + */ +static void +expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, + Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_append_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_inherited_rtentry(root, rte, rti, rel); + } +} + +/* + * expand_inherited_rtentry + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. + * + * Note that the original RTE is considered to represent the whole + * inheritance set. The first of the generated RTEs is an RTE for the same + * table, but with inh = false, to represent the parent table in its role + * as a simple member of the inheritance set. + * + * A childless table is never considered to be an inheritance set. For + * regular inheritance, a parent RTE must always have at least two associated + * AppendRelInfos: one corresponding to the parent table as a simple member of + * inheritance set and one or more corresponding to the actual children. + */ +static void +expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, + RelOptInfo *rel) +{ + Oid parentOID; + PlanRowMark *oldrc; + LOCKMODE lockmode = rte->rellockmode; + List *inhOIDs; + ListCell *l; + int num_children; + int num_children_added = 0; + + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); + parentOID = rte->relid; + + /* Scan for all members of inheritance set, acquire needed locks */ + inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); + + /* + * Check that there's at least one descendant, else treat as no-child + * case. This could happen despite has_subclass() check performed by + * subquery_planner, if table once had a child but no longer does. + */ + num_children = list_length(inhOIDs); + if (num_children < 2) + { + /* Clear flag before returning */ + rte->inh = false; + return; + } + + /* + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. + */ + oldrc = get_plan_rowmark(root->rowMarks, rti); + Assert(oldrc == NULL || oldrc->isParent); + + /* + * Must expand PlannerInfo arrays by num_children before we can add + * children. + */ + expand_planner_arrays(root, num_children); + + foreach(l, inhOIDs) + { + Oid childOID = lfirst_oid(l); + Relation newrelation; + RangeTblEntry *childrte; + Index childRTindex; + + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + + /* + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. + */ + if (RELATION_IS_OTHER_TEMP(newrelation)) + { + heap_close(newrelation, lockmode); + continue; + } + + (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation, &childrte, + &childRTindex); + Assert(childrte != NULL); + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + Assert(childRTindex > 0); + + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; + } + + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } +} + +/* + * expand_partitioned_rtentry + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * when this function is called for the root partitioned table. + */ +static void +expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel) +{ + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; + int i; + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; + + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); + + /* + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. + */ + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); + + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + expand_planner_arrays(root, bms_num_members(partindexes)); + + /* + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. + */ + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); + + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) + { + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + RangeTblEntry *childrte; + Index childRTindex; + + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); + + /* + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. + */ + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } + + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation, + &childrte, &childRTindex); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; + + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + + /* If the child is partitioned itself, expand it too. */ + if (childrel->part_scheme) + { + Assert(childrte->inh); + expand_partitioned_rtentry(root, childrte, childRTindex, + childrel); + } + } + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); + } +} + +/* + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. + * + * The return value is the RelOptInfo that's added. + * + * PlanRowMarks still carry the top-parent's RTI, and the top-parent's + * allMarkTypes field still accumulates values from all descendents. + * + * "parentrte" and "parentRTindex" are immediate parent's RTE and + * RTI. "top_parentrc" is top parent's PlanRowMark. + */ +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p) +{ + Query *parse = root->parse; + Oid childOID = RelationGetRelid(childrel); + RangeTblEntry *childrte; + Index childRTindex; + AppendRelInfo *appinfo; + RelOptInfo *childrelopt; + + /* + * Build an RTE for the child, and attach to query's rangetable list. We + * copy most fields of the parent's RTE, but replace relation OID and + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties + * individual children may have. (This is an intentional choice to make + * inherited RLS work like regular permissions checks.) The parent + * securityQuals will be propagated to children along with other base + * restriction clauses, so we don't need to do it here. + */ + childrte = copyObject(parentrte); + *childrte_p = childrte; + childrte->relid = childOID; + childrte->relkind = childrel->rd_rel->relkind; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); + childrte->requiredPerms = 0; + childrte->securityQuals = NIL; + parse->rtable = lappend(parse->rtable, childrte); + childRTindex = list_length(parse->rtable); + *childRTindex_p = childRTindex; + + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + childrel->rd_att, + childOID, + childrel->rd_rel->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); + + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); + } + + /* + * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE. + */ + if (top_parentrc) + { + PlanRowMark *childrc = makeNode(PlanRowMark); + + childrc->rti = childRTindex; + childrc->prti = top_parentrc->rti; + childrc->rowmarkId = top_parentrc->rowmarkId; + /* Reselect rowmark type, because relkind might not match parent */ + childrc->markType = select_rowmark_type(childrte, + top_parentrc->strength); + childrc->allMarkTypes = (1 << childrc->markType); + childrc->strength = top_parentrc->strength; + childrc->waitPolicy = top_parentrc->waitPolicy; + + /* + * We mark RowMarks for partitioned child tables as parent RowMarks so + * that the executor ignores them (except their existence means that + * the child tables be locked using appropriate mode). + */ + childrc->isParent = (childrte->relkind == RELKIND_PARTITIONED_TABLE); + + /* Include child's rowmark type in top parent's allMarkTypes */ + top_parentrc->allMarkTypes |= childrc->allMarkTypes; + + root->rowMarks = lappend(root->rowMarks, childrc); + } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need to create one, + * because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_append_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; +} + +/* + * build_append_child_rel + * Build a RelOptInfo for child relation of an append rel + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields based on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turn out to be self-contradictory, the + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + List *childquals; + ListCell *lc; + bool have_const_false_cq; + Index cq_min_security; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = false; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, parent->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual = (Node *) rinfo->clause; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, childqual, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might + * have securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals.) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except + * that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* + * not likely that we'd see constants here, so no + * check + */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, + false, + false, + security_level, + NULL, NULL, + NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* Set child's version of baserestrictinfo. */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; +} diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 5921e893c1..b2637d0e89 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -20,6 +20,7 @@ #include "foreign/fdwapi.h" #include "nodes/extensible.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index fa39afcfe6..8ac73b4c34 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -17,6 +17,7 @@ #include <limits.h> #include "miscadmin.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 64787de16f..9bb472fd98 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -44,6 +44,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h new file mode 100644 index 0000000000..31011922f0 --- /dev/null +++ b/src/include/optimizer/appendinfo.h @@ -0,0 +1,43 @@ +/*------------------------------------------------------------------------- + * + * appendinfo.h + * Routines for mapping expressions between append rel parent(s) and + * children + * + * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/optimizer/appendinfo.h + * + *------------------------------------------------------------------------- + */ +#ifndef APPENDINFO_H +#define APPENDINFO_H + +#include "nodes/plannodes.h" +#include "nodes/relation.h" +#include "utils/relcache.h" + +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parentrel, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childreltype, + Index childRTindex); +extern Bitmapset *translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars); +extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, + int nappinfos, AppendRelInfo **appinfos); + +extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, + Relids child_relids, + Relids top_parent_relids); + +extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root, + Relids relids, int *nappinfos); + +extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, + SpecialJoinInfo *parent_sjinfo, + Relids left_relids, Relids right_relids); +extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, + Relids child_relids, Relids top_parent_relids); + +#endif /* APPENDINFO_H */ diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h new file mode 100644 index 0000000000..c0bd54d9ca --- /dev/null +++ b/src/include/optimizer/inherit.h @@ -0,0 +1,25 @@ +/*------------------------------------------------------------------------- + * + * inherit.h + * prototypes for inherit.c. + * + * + * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/optimizer/inherit.h + * + *------------------------------------------------------------------------- + */ +#ifndef INHERIT_H +#define INHERIT_H + +#include "nodes/relation.h" + +/* + * inherit.c + * utilities for dealing with append relations + */ +extern void expand_inherited_tables(PlannerInfo *root); + +#endif /* INHERIT_H */ diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 9c0795262f..55a69267c6 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -48,24 +48,5 @@ extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); */ extern RelOptInfo *plan_set_operations(PlannerInfo *root); -extern void expand_inherited_tables(PlannerInfo *root); -extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, - int nappinfos, AppendRelInfo **appinfos); -extern AppendRelInfo *make_append_rel_info(RelOptInfo *parentrel, - RangeTblEntry *parentrte, - TupleDesc childdesc, Oid childoid, Oid childreltype, - Index childRTindex); -extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, - Relids child_relids, - Relids top_parent_relids); - -extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root, - Relids relids, int *nappinfos); - -extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, - SpecialJoinInfo *parent_sjinfo, - Relids left_relids, Relids right_relids); -extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, - Relids child_relids, Relids top_parent_relids); #endif /* PREP_H */ -- 2.11.0
v11-0005-Teach-planner-to-only-process-unpruned-partition.patch (text/plain, 6.9K) download From 3f1a01e84d1ca093561804dbd137d3ef735e4bd6 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 15 Oct 2018 10:59:24 +0900 Subject: [PATCH v11 5/6] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/inherit.c | 1 + src/backend/optimizer/util/relnode.c | 3 +++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/relation.h | 4 ++++ 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 23335a1c2d..4526299dbd 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1424,6 +1424,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index db277385d5..32c6cac5c3 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6816,7 +6816,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -6824,9 +6826,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -6907,7 +6907,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -6919,7 +6918,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -6929,9 +6930,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 2224c5911f..b3da21e6e8 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -350,6 +350,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, /* Perform pruning. */ partindexes = prune_append_rel_partitions(parentrel); + parentrel->live_parts = partindexes; /* Must expand PlannerInfo arrays before we can add children. */ expand_planner_arrays(root, bms_num_members(partindexes)); diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 8ac73b4c34..74d1dcaa20 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1748,6 +1748,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 9bb472fd98..088b191bdd 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -441,29 +441,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 0303a454ce..a0a71fcd62 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -730,6 +730,10 @@ typedef struct RelOptInfo Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Indexes into part_rels of the non-NULL + * RelOptInfos of unpruned partitions; exists + * to avoid having to iterate over the entire + * part_rels array to filter NULL entries. */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.11.0
v11-0006-Do-not-lock-all-partitions-at-the-beginning.patch (text/plain, 1.8K) download From bb09283379485fef01e94009c121181d4e086aa5 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 12 Oct 2018 19:12:53 +0900 Subject: [PATCH v11 6/6] Do not lock all partitions at the beginning --- src/backend/optimizer/util/inherit.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index b3da21e6e8..9aeba5897d 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -317,10 +317,6 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, * * Partitions are added to the query in order in which they are found in * the parent's PartitionDesc. - * - * Note: even though only the unpruned partitions will be added to the - * resulting plan, this still locks *all* partitions via find_all_inheritors - * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, @@ -334,10 +330,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, parentrel->inh_root_parent : parentRTindex; - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - /* * Initialize partitioned_child_rels to contain this RT index. * @@ -373,8 +365,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, RangeTblEntry *childrte; Index childRTindex; - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); + newrelation = heap_open(childOID, lockmode); Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-09 00:09  David Rowley <david.rowley@2ndquadrant.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  2 siblings, 1 reply; 265+ messages in thread

From: David Rowley @ 2019-01-09 00:09 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On Tue, 8 Jan 2019 at 19:30, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
> Rebased due to changed copyright year in prepunion.c.  Also realized that
> the new files added by patch 0004 still had 2018 in them.

I've made a pass over 0001. There's probably enough for you to look at
while I look at 0002 and the others.

0001

1. In your doc changes, just below a paragraph that you removed,
there's a paragraph starting "Both of these behaviors are likely to be
changed in a future release". This needs to be fixed since you've
removed the first of the two reasons.

2. This part should be left alone.

-    technique similar to partition pruning.  While it is primarily used
-    for partitioning implemented using the legacy inheritance method, it can be
-    used for other purposes, including with declarative partitioning.
+    technique similar to partition pruning.  It is primarily used
+    for partitioning implemented using the legacy inheritance method.

Looking at set_inherit_target_rel_sizes(), constraint exclusion still
is applied to partitions, it's just applied after pruning, according
to:

if (did_pruning && !bms_is_member(appinfo->child_relid, live_children))
{
/* This partition was pruned; skip it. */
set_dummy_rel_pathlist(childrel);
continue;
}

if (relation_excluded_by_constraints(root, childrel, childRTE))

3. add_child_rel_equivalences().  You're replacing parent EMs with
their child equivalent, but only when the eclass has no volatile
functions. Is this really what you want? I think this would misbehave
if we ever allowed: UPDATE ... SET .. ORDER BY, of which there's a
legitimate use case of wanting to reduce the chances of deadlocks
caused by non-deterministic UPDATE order.  Or if you think skipping
the volatile eclasses is fine today, then I think the comment you've
added to add_child_rel_equivalences should mention that.

4. Do you think it's okay that add_child_rel_equivalences() does not
update the ec_relids when removing the member?

UPDATE: I see you're likely leaving this alone since you're only doing
a shallow copy of the eclasses in
adjust_inherited_target_child_root(). It seems like a pretty bad idea
to do a shallow copy there.

5. What's CE?

+ /* CE failed, so finish copying/modifying join quals. */

6. Typo:

+ * ass dummy.  We must do this in this phase so that the rel's

ass -> as

7. There's no accumulation going on here:

+ /*
+ * Accumulate size information from each live child.
+ */
+ Assert(childrel->rows > 0);

8. Any point in this? We're about to loop again anyway...

+ /*
+ * If child is dummy, ignore it.
+ */
+ if (IS_DUMMY_REL(childrel))
+ continue;
+ }

9. It's a bit confusing to mention SELECT in this comment. The Assert
ensures it's an UPDATE or DELETE.

+ /*
+ * For UPDATE/DELETE queries, the top parent can only ever be a table.
+ * As a contrast, it could be a UNION ALL subquery in the case of SELECT.
+ */
+ Assert(root->parse->commandType == CMD_UPDATE ||
+    root->parse->commandType == CMD_DELETE);

10. I'd say the subroot assignment can go after the IS_DUMMY_REL
check. Keeping that loop as tight as possible for pruned rels seems
like a good idea.

+ subroot = root->inh_target_child_roots[appinfo->child_relid];
+ Assert(subroot->parse->resultRelation > 0);
+ childrel = find_base_rel(root, appinfo->child_relid);
+
+ /* Ignore excluded/pruned children. */
+ if (IS_DUMMY_REL(childrel))
+ continue;

11. I don't think you should reuse the childrel variable here:

+ childrel->reloptkind = RELOPT_BASEREL;
+
+ Assert(subroot->join_rel_list == NIL);
+ Assert(subroot->join_rel_hash == NULL);
+
+ /* Perform join planning and save the resulting RelOptInfo. */
+ childrel = make_rel_from_joinlist(subroot, translated_joinlist);
+
+ /*
+ * Remember this child target rel.  inheritance_planner will perform
+ * the remaining steps of planning for each child relation separately.
+ * Specifically, it will call grouping_planner on every
+ * RelOptInfo contained in the inh_target_child_rels list, each of
+ * which represents the source of tuples to be modified for a given
+ * target child rel.
+ */
+ root->inh_target_child_joinrels =
+ lappend(root->inh_target_child_joinrels, childrel);

12. The following comment makes less sense now that you've modified
the previous paragraph:

+ * Fortunately, the UPDATE/DELETE target can never be the nullable side of an
+ * outer join, so it's OK to generate the plan this way.

This text used to refer to:

but target inheritance has to be expanded at
 * the top.  The reason is that for UPDATE, each target relation needs a
 * different targetlist matching its own column set.  Fortunately,
 * the UPDATE/DELETE target can never be the nullable side of an outer join,
 * so it's OK to generate the plan this way.

you no longer describe plan as being expanded from the top rather than
at the bottom, which IMO is what "this way" refers to.

13. "tree is" -> "tree are" (references is plural)

+ * references in the join tree to the original target relation that's the
+ * root parent of the inheritance tree is replaced by each of its

14. Any reason to move this line from its original location?

  Assert(parse->commandType != CMD_INSERT);
+ parent_rte = planner_rt_fetch(top_parentRTindex, root);

Previously it was assigned just before it was needed and there's a
fast path out after where you moved it to and where it was.

15. relation_excluded_by_constraints(), the switch
(constraint_exclusion), you could consider turning that into

if (constraint_exclusion == CONSTRAINT_EXCLUSION_OFF)
    return false;
/*
 * When constraint_exclusion is set to 'partition' we only handle
 * OTHER_MEMBER_RELs.
 */
else if (constraint_exclusion == CONSTRAINT_EXCLUSION_PARTITION &&
         rel->reloptkind != RELOPT_OTHER_MEMBER_REL)
    return false;

When I wrote that code I was trying my best to make the complex rules
as simple as possible by separating them out.  The rules have become
quite simple after your change, so it probably does not warrant having
the switch.

16. I think the following comment needs to explain how large this
array is and what indexes it.  The current comment would have you
think there are only enough elements to store PlannerInfos for child
rels and leaves you guessing about what they're indexed by.

+ /*
+ * PlannerInfos corresponding to each inheritance child targets.
+ * Content of each PlannerInfo is same as the parent PlannerInfo, except
+ * for the parse tree which is a translated copy of the parent's parse
+ * tree.
+ */
+ struct PlannerInfo **inh_target_child_roots;

17. I'm getting an assert failure in add_paths_to_append_rel() for
Assert(parallel_workers > 0) during the partition_join tests.

-- 
 David Rowley                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-01-09 02:08  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 1 reply; 265+ messages in thread

From: Imai, Yoshikazu @ 2019-01-09 02:08 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Hi,

I ran the performance tests for no prepared query and for prepared query with
plan_cache_mode='auto' and plan_cache_mode='force_custom_plan'. I also changed
number of partitions as 256 or 4096. I ran the tests on master and v9-patched.

[settings]
plan_cache_mode = 'auto' or 'force_custom_plan'
max_parallel_workers = 0
max_parallel_workers_per_gather = 0
max_locks_per_transaction = 4096

[partitioning table definitions(with 4096 partitions)]
create table rt (a int, b int, c int) partition by range (a);

\o /dev/null
select 'create table rt' || x::text || ' partition of rt for values from (' ||
 (x)::text || ') to (' || (x+1)::text || ');' from generate_series(1, 4096) x;
\gexec
\o

[pgbench(with 4096 partitions)]
no prepared: pgbench -n -f select4096.sql -T 60
prepared:    pgbench -n -f select4096.sql -T 60 -M prepared

[select4096.sql]
\set a random(1, 4096)
select a from rt where a = :a;

[results]
master:
  part_num  no-prepared   auto   force_custom_plan  (1-auto/force_custom_plan)
       256          604    571                 576                        0.01
      4096         17.5   17.5                15.1                       -0.16
		
patched:
  part_num  no-prepared   auto   force_custom_plan
       256         8614   9446                9384                      -0.006
      4096         7158   7165                7864                       0.089


There are almost no difference between auto and force_custom_plan with 256
partitions, but there are difference between auto and force_custom_plan with
4096 partitions. While auto is faster than force_custom_plan on master,
force_custom_plan is faster than auto on patched.

I wonder why force_custom_plan is faster than auto after applied the patch.

When we use PREPARE-EXECUTE, a generic plan is created and used if its cost is
cheaper than creating and using a custom plan with plan_cache_mode='auto',
while a custom plan is always created and used with plan_cache_mode='force_custom_plan'.
So one can think the difference in above results is because of creating or
using a generic plan.

I checked how many times a generic plan is created during executing pgbench and
found a generic plan is created only once and custom plans are created at other
times with plan_cache_mode='auto'. I also checked the time of creating a
generic plan, but it didn't take so much(250ms or so with 4096 partitions). So
the time of creating a generic plan does not affect the performance.

Currently, a generic plan is created at sixth time of executing EXECUTE query.
I changed it to more later (ex. at 400,000th time of executing EXECUTE query on
master with 4096 partitions, because 7000TPS x 60sec=420,0000 transactions are
run while executing pgbench.), then there are almost no difference between auto
and force_custom_plan. I think that creation of a generic plan affects the time
of executing queries which are ordered after creating generic plan.

If my assumption is right, we can expect some additional process is occurred at
executing queries ordered after creating a generic plan, which results in auto is
slower than force_custom_plan because of additional process. But looking at
above results, on master with 4096 partitions, auto is faster than force_custom_plan.
So now I am confused.

Do you have any ideas what does affect the performance?

--
Yoshikazu Imai


^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-09 14:41  Alvaro Herrera <alvherre@2ndquadrant.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  2 siblings, 1 reply; 265+ messages in thread

From: Alvaro Herrera @ 2019-01-09 14:41 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; David Rowley <david.rowley@2ndquadrant.com>; pgsql-hackers

> From 3b86331dd5a2368adc39c9fef92f3dd09d817a08 Mon Sep 17 00:00:00 2001
> From: amit <amitlangote09@gmail.com>
> Date: Wed, 7 Nov 2018 16:51:31 +0900
> Subject: [PATCH v11 4/6] Move inheritance expansion code into its own file

I wonder if it would make sense to commit 0004 ahead of the rest?  To
avoid carrying this huge one over and over ...

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-09 15:16  Amit Langote <amitlangote09@gmail.com>
  parent: Alvaro Herrera <alvherre@2ndquadrant.com>
  0 siblings, 1 reply; 265+ messages in thread

From: Amit Langote @ 2019-01-09 15:16 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; David Rowley <david.rowley@2ndquadrant.com>; pgsql-hackers

On Wed, Jan 9, 2019 at 11:41 PM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
> > From 3b86331dd5a2368adc39c9fef92f3dd09d817a08 Mon Sep 17 00:00:00 2001
> > From: amit <amitlangote09@gmail.com>
> > Date: Wed, 7 Nov 2018 16:51:31 +0900
> > Subject: [PATCH v11 4/6] Move inheritance expansion code into its own file
>
> I wonder if it would make sense to commit 0004 ahead of the rest?  To
> avoid carrying this huge one over and over ...

Maybe a good idea.  I will rearrange the patches that  way tomorrow.

Thanks,
Amit




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-01-10 06:37  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  2 siblings, 0 replies; 265+ messages in thread

From: Imai, Yoshikazu @ 2019-01-10 06:37 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Hi Amit,

On Mon, Jan 7, 2019 at 6:30 PM, Amit Langote wrote:
> On 2018/12/27 20:25, Amit Langote wrote:
> > Here's the v10 of the patch.  I didn't get chance to do more changes
> > than those described above and address Imai-san's review comments
> yesterday.
> >
> > Have a wonderful new year!  I'll be back on January 7 to reply on this
> thread.
> 
> Rebased due to changed copyright year in prepunion.c.  Also realized that
> the new files added by patch 0004 still had 2018 in them.

Thank you for new patches.


I also have some comments on 0001, set_inherit_target_rel_sizes().

In set_inherit_target_rel_sizes():

Some codes are placed not the same order as set_append_rel_size().

0001: at line 325-326,
+	ListCell   *l;
+	bool		has_live_children;

In set_append_rel_size(), "has_live_children" is above of the "ListCell *l";

0001: at line 582-603
+		if (IS_DUMMY_REL(childrel))
+			continue;
+
...
+		Assert(childrel->rows > 0);
+
+		/* We have at least one live child. */
+		has_live_children = true;

In set_append_rel_size(), 
+		/* We have at least one live child. */
+		has_live_children = true;
is directly under of
+		if (IS_DUMMY_REL(childrel))
+			continue;

0001: at line 606-622,
+	if (!has_live_children)
+	{
+		/*
+		 * All children were excluded by constraints, so mark the relation
+		 * ass dummy.  We must do this in this phase so that the rel's
+		 * dummy-ness is visible when we generate paths for other rels.
+		 */
+		set_dummy_rel_pathlist(rel);
+	}
+	else
+		/*
+		 * Set a non-zero value here to cope with the caller's requirement
+		 * that non-dummy relations are actually not empty.  We don't try to
+		 * be accurate here, because we're not going to create a path that
+		 * combines the children outputs.
+		 */
+		rel->rows = 1;

In set_append_rel_size(), a condition of if clause is not !has_live_children but 
has_live_children.

I also noticed there isn't else block while there is if block.

--
Yoshikazu Imai



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-10 08:41  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Amit Langote <amitlangote09@gmail.com>
  0 siblings, 3 replies; 265+ messages in thread

From: Amit Langote @ 2019-01-10 08:41 UTC (permalink / raw)
  To: Amit Langote <amitlangote09@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; David Rowley <david.rowley@2ndquadrant.com>; pgsql-hackers

On 2019/01/10 0:16, Amit Langote wrote:
> On Wed, Jan 9, 2019 at 11:41 PM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
>>> From 3b86331dd5a2368adc39c9fef92f3dd09d817a08 Mon Sep 17 00:00:00 2001
>>> From: amit <amitlangote09@gmail.com>
>>> Date: Wed, 7 Nov 2018 16:51:31 +0900
>>> Subject: [PATCH v11 4/6] Move inheritance expansion code into its own file
>>
>> I wonder if it would make sense to commit 0004 ahead of the rest?  To
>> avoid carrying this huge one over and over ...
> 
> Maybe a good idea.  I will rearrange the patches that  way tomorrow.

Here's v12, which is more or less same as v11 but with the order of
patches switched so that the code movement patch is first.  Note that the
attached v12-0001 contains no functional changes (but there's tiny note in
the commit message mentioning the addition of a tiny function which is
just old code).

In the v13 that I will try to post tomorrow, I will have hopefully
addressed David's and Imai-san's review comments.  Thank you both!

Regards,
Amit

Attachments: download all (6 files, .tar.gz)

v12-0001-Move-inheritance-expansion-code-into-its-own-fil.patch (text/plain, 88.3K) download From bc6f2af5d9fb09d2fb29cd2ac07ffdaa2fbaef26 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Thu, 10 Jan 2019 13:36:46 +0900 Subject: [PATCH v12 1/6] Move inheritance expansion code into its own file This commit moves expand_inherited_tables and underlings currently in optimizer/prep/prepunionc.c to optimizer/utils/inherit.c. All of the AppendRelInfo based expression manipulation routines are moved to optimizer/utils/appendinfo.c. This commit mostly moves the code and contains no functional changes. One exception though is the introduction of make_append_rel_info, but it's still just moving around code. --- src/backend/optimizer/path/allpaths.c | 2 + src/backend/optimizer/path/equivclass.c | 1 + src/backend/optimizer/path/joinrels.c | 1 + src/backend/optimizer/plan/planmain.c | 2 + src/backend/optimizer/plan/planner.c | 2 + src/backend/optimizer/prep/prepunion.c | 1202 ------------------------------- src/backend/optimizer/util/Makefile | 5 +- src/backend/optimizer/util/appendinfo.c | 824 +++++++++++++++++++++ src/backend/optimizer/util/inherit.c | 451 ++++++++++++ src/backend/optimizer/util/pathnode.c | 1 + src/backend/optimizer/util/relnode.c | 1 + src/backend/partitioning/partprune.c | 1 + src/include/optimizer/appendinfo.h | 42 ++ src/include/optimizer/inherit.h | 25 + src/include/optimizer/prep.h | 18 - 15 files changed, 1356 insertions(+), 1222 deletions(-) create mode 100644 src/backend/optimizer/util/appendinfo.c create mode 100644 src/backend/optimizer/util/inherit.c create mode 100644 src/include/optimizer/appendinfo.h create mode 100644 src/include/optimizer/inherit.h diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 256fe16cdb..8bd71a9825 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -30,6 +30,8 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "optimizer/inherit.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/geqo.h" diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 0d994bd9ae..7fad7ad1bf 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -22,6 +22,7 @@ #include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 67b9ca83a7..22ca4d999a 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -15,6 +15,7 @@ #include "postgres.h" #include "miscadmin.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 0e0a543708..fc97a1bb50 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -20,7 +20,9 @@ */ #include "postgres.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" +#include "optimizer/inherit.h" #include "optimizer/orclauses.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 3e33a17a5b..98acb58304 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -37,8 +37,10 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" +#include "optimizer/inherit.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 1d280c205e..5d59385ef7 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -12,11 +12,6 @@ * case, but most of the heavy lifting for that is done elsewhere, * notably in prepjointree.c and allpaths.c. * - * There is also some code here to support planning of queries that use - * inheritance (SELECT FROM foo*). Inheritance trees are converted into - * append relations, and thenceforth share code with the UNION ALL case. - * - * * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -54,13 +49,6 @@ #include "utils/syscache.h" -typedef struct -{ - PlannerInfo *root; - int nappinfos; - AppendRelInfo **appinfos; -} adjust_appendrel_attrs_context; - static RelOptInfo *recurse_set_operations(Node *setOp, PlannerInfo *root, List *colTypes, List *colCollations, bool junkOK, @@ -99,31 +87,6 @@ static List *generate_append_tlist(List *colTypes, List *colCollations, List *input_tlists, List *refnames_tlist); static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist); -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); -static void expand_partitioned_rtentry(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); -static void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); -static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars); -static Node *adjust_appendrel_attrs_mutator(Node *node, - adjust_appendrel_attrs_context *context); -static Relids adjust_child_relids(Relids relids, int nappinfos, - AppendRelInfo **appinfos); -static List *adjust_inherited_tlist(List *tlist, - AppendRelInfo *context); /* @@ -1460,1168 +1423,3 @@ generate_setop_grouplist(SetOperationStmt *op, List *targetlist) Assert(lg == NULL); return grouplist; } - - -/* - * expand_inherited_tables - * Expand each rangetable entry that represents an inheritance set - * into an "append relation". At the conclusion of this process, - * the "inh" flag is set in all and only those RTEs that are append - * relation parents. - */ -void -expand_inherited_tables(PlannerInfo *root) -{ - Index nrtes; - Index rti; - ListCell *rl; - - /* - * expand_inherited_rtentry may add RTEs to parse->rtable. The function is - * expected to recursively handle any RTEs that it creates with inh=true. - * So just scan as far as the original end of the rtable list. - */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) - { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); - - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); - } -} - -/* - * expand_inherited_rtentry - * Check whether a rangetable entry represents an inheritance set. - * If so, add entries for all the child tables to the query's - * rangetable, and build AppendRelInfo nodes for all the child tables - * and add them to root->append_rel_list. If not, clear the entry's - * "inh" flag to prevent later code from looking for AppendRelInfos. - * - * Note that the original RTE is considered to represent the whole - * inheritance set. The first of the generated RTEs is an RTE for the same - * table, but with inh = false, to represent the parent table in its role - * as a simple member of the inheritance set. - * - * A childless table is never considered to be an inheritance set. For - * regular inheritance, a parent RTE must always have at least two associated - * AppendRelInfos: one corresponding to the parent table as a simple member of - * inheritance set and one or more corresponding to the actual children. - * Since a partitioned table is not scanned, it might have only one associated - * AppendRelInfo. - */ -static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) -{ - Oid parentOID; - PlanRowMark *oldrc; - Relation oldrelation; - LOCKMODE lockmode; - List *inhOIDs; - ListCell *l; - - /* Does RT entry allow inheritance? */ - if (!rte->inh) - return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } - /* Fast path for common case of childless table */ - parentOID = rte->relid; - if (!has_subclass(parentOID)) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * The rewriter should already have obtained an appropriate lock on each - * relation named in the query. However, for each child relation we add - * to the query, we must obtain an appropriate lock, because this will be - * the first use of those relations in the parse/rewrite/plan pipeline. - * Child rels should use the same lockmode as their parent. - */ - lockmode = rte->rellockmode; - - /* Scan for all members of inheritance set, acquire needed locks */ - inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); - - /* - * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite above has_subclass() check, if table - * once had a child but no longer does. - */ - if (list_length(inhOIDs) < 2) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * If parent relation is selected FOR UPDATE/SHARE, we need to mark its - * PlanRowMark as isParent = true, and generate a new PlanRowMark for each - * child. - */ - oldrc = get_plan_rowmark(root->rowMarks, rti); - if (oldrc) - oldrc->isParent = true; - - /* - * Must open the parent relation to examine its tupdesc. We need not lock - * it; we assume the rewriter already did. - */ - oldrelation = heap_open(parentOID, NoLock); - - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) - { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); - - /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. - */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); - } - else - { - List *appinfos = NIL; - RangeTblEntry *childrte; - Index childRTindex; - - /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. - */ - foreach(l, inhOIDs) - { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = heap_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - heap_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - heap_close(newrelation, NoLock); - } - - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); - - } - - heap_close(oldrelation, NoLock); -} - -/* - * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. - */ -static void -expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) -{ - int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); - - check_stack_depth(); - - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); - - /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. - */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); - - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); - - /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. - */ - if (partdesc->nparts == 0) - { - parentrte->inh = false; - return; - } - - for (i = 0; i < partdesc->nparts; i++) - { - Oid childOID = partdesc->oids[i]; - Relation childrel; - - /* Open rel; we already have required locks */ - childrel = heap_open(childOID, NoLock); - - /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. - */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); - - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); - - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); - - /* Close child relation, but keep locks */ - heap_close(childrel, NoLock); - } -} - -/* - * expand_single_inheritance_child - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. - * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) - * - * PlanRowMarks still carry the top-parent's RTI, and the top-parent's - * allMarkTypes field still accumulates values from all descendents. - * - * "parentrte" and "parentRTindex" are immediate parent's RTE and - * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. - */ -static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) -{ - Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); - Oid childOID = RelationGetRelid(childrel); - RangeTblEntry *childrte; - Index childRTindex; - AppendRelInfo *appinfo; - - /* - * Build an RTE for the child, and attach to query's rangetable list. We - * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh = false. Also, set requiredPerms to zero since - * all required permissions checks are done on the original RTE. Likewise, - * set the child's securityQuals to empty, because we only want to apply - * the parent's RLS conditions regardless of what RLS properties - * individual children may have. (This is an intentional choice to make - * inherited RLS work like regular permissions checks.) The parent - * securityQuals will be propagated to children along with other base - * restriction clauses, so we don't need to do it here. - */ - childrte = copyObject(parentrte); - *childrte_p = childrte; - childrte->relid = childOID; - childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) - childrte->inh = true; - else - childrte->inh = false; - childrte->requiredPerms = 0; - childrte->securityQuals = NIL; - parse->rtable = lappend(parse->rtable, childrte); - childRTindex = list_length(parse->rtable); - *childRTindex_p = childRTindex; - - /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. - */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) - { - appinfo = makeNode(AppendRelInfo); - appinfo->parent_relid = parentRTindex; - appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->rd_rel->reltype; - appinfo->child_reltype = childrel->rd_rel->reltype; - make_inh_translation_list(parentrel, childrel, childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = parentOID; - *appinfos = lappend(*appinfos, appinfo); - - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } - } - - /* - * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE. - */ - if (top_parentrc) - { - PlanRowMark *childrc = makeNode(PlanRowMark); - - childrc->rti = childRTindex; - childrc->prti = top_parentrc->rti; - childrc->rowmarkId = top_parentrc->rowmarkId; - /* Reselect rowmark type, because relkind might not match parent */ - childrc->markType = select_rowmark_type(childrte, - top_parentrc->strength); - childrc->allMarkTypes = (1 << childrc->markType); - childrc->strength = top_parentrc->strength; - childrc->waitPolicy = top_parentrc->waitPolicy; - - /* - * We mark RowMarks for partitioned child tables as parent RowMarks so - * that the executor ignores them (except their existence means that - * the child tables be locked using appropriate mode). - */ - childrc->isParent = (childrte->relkind == RELKIND_PARTITIONED_TABLE); - - /* Include child's rowmark type in top parent's allMarkTypes */ - top_parentrc->allMarkTypes |= childrc->allMarkTypes; - - root->rowMarks = lappend(root->rowMarks, childrc); - } -} - -/* - * make_inh_translation_list - * Build the list of translations from parent Vars to child Vars for - * an inheritance child. - * - * For paranoia's sake, we match type/collation as well as attribute name. - */ -static void -make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, - List **translated_vars) -{ - List *vars = NIL; - TupleDesc old_tupdesc = RelationGetDescr(oldrelation); - TupleDesc new_tupdesc = RelationGetDescr(newrelation); - Oid new_relid = RelationGetRelid(newrelation); - int oldnatts = old_tupdesc->natts; - int newnatts = new_tupdesc->natts; - int old_attno; - int new_attno = 0; - - for (old_attno = 0; old_attno < oldnatts; old_attno++) - { - Form_pg_attribute att; - char *attname; - Oid atttypid; - int32 atttypmod; - Oid attcollation; - - att = TupleDescAttr(old_tupdesc, old_attno); - if (att->attisdropped) - { - /* Just put NULL into this list entry */ - vars = lappend(vars, NULL); - continue; - } - attname = NameStr(att->attname); - atttypid = att->atttypid; - atttypmod = att->atttypmod; - attcollation = att->attcollation; - - /* - * When we are generating the "translation list" for the parent table - * of an inheritance set, no need to search for matches. - */ - if (oldrelation == newrelation) - { - vars = lappend(vars, makeVar(newvarno, - (AttrNumber) (old_attno + 1), - atttypid, - atttypmod, - attcollation, - 0)); - continue; - } - - /* - * Otherwise we have to search for the matching column by name. - * There's no guarantee it'll have the same column position, because - * of cases like ALTER TABLE ADD COLUMN and multiple inheritance. - * However, in simple cases, the relative order of columns is mostly - * the same in both relations, so try the column of newrelation that - * follows immediately after the one that we just found, and if that - * fails, let syscache handle it. - */ - if (new_attno >= newnatts || - (att = TupleDescAttr(new_tupdesc, new_attno))->attisdropped || - strcmp(attname, NameStr(att->attname)) != 0) - { - HeapTuple newtup; - - newtup = SearchSysCacheAttName(new_relid, attname); - if (!newtup) - elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, RelationGetRelationName(newrelation)); - new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; - ReleaseSysCache(newtup); - - att = TupleDescAttr(new_tupdesc, new_attno); - } - - /* Found it, check type and collation match */ - if (atttypid != att->atttypid || atttypmod != att->atttypmod) - elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, RelationGetRelationName(newrelation)); - if (attcollation != att->attcollation) - elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, RelationGetRelationName(newrelation)); - - vars = lappend(vars, makeVar(newvarno, - (AttrNumber) (new_attno + 1), - atttypid, - atttypmod, - attcollation, - 0)); - new_attno++; - } - - *translated_vars = vars; -} - -/* - * translate_col_privs - * Translate a bitmapset representing per-column privileges from the - * parent rel's attribute numbering to the child's. - * - * The only surprise here is that we don't translate a parent whole-row - * reference into a child whole-row reference. That would mean requiring - * permissions on all child columns, which is overly strict, since the - * query is really only going to reference the inherited columns. Instead - * we set the per-column bits for all inherited columns. - */ -static Bitmapset * -translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars) -{ - Bitmapset *child_privs = NULL; - bool whole_row; - int attno; - ListCell *lc; - - /* System attributes have the same numbers in all tables */ - for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) - { - if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - attno - FirstLowInvalidHeapAttributeNumber); - } - - /* Check if parent has whole-row reference */ - whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, - parent_privs); - - /* And now translate the regular user attributes, using the vars list */ - attno = InvalidAttrNumber; - foreach(lc, translated_vars) - { - Var *var = lfirst_node(Var, lc); - - attno++; - if (var == NULL) /* ignore dropped columns */ - continue; - if (whole_row || - bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - var->varattno - FirstLowInvalidHeapAttributeNumber); - } - - return child_privs; -} - -/* - * adjust_appendrel_attrs - * Copy the specified query or expression and translate Vars referring to a - * parent rel to refer to the corresponding child rel instead. We also - * update rtindexes appearing outside Vars, such as resultRelation and - * jointree relids. - * - * Note: this is only applied after conversion of sublinks to subplans, - * so we don't need to cope with recursion into sub-queries. - * - * Note: this is not hugely different from what pullup_replace_vars() does; - * maybe we should try to fold the two routines together. - */ -Node * -adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, - AppendRelInfo **appinfos) -{ - Node *result; - adjust_appendrel_attrs_context context; - - context.root = root; - context.nappinfos = nappinfos; - context.appinfos = appinfos; - - /* If there's nothing to adjust, don't call this function. */ - Assert(nappinfos >= 1 && appinfos != NULL); - - /* - * Must be prepared to start with a Query or a bare expression tree. - */ - if (node && IsA(node, Query)) - { - Query *newnode; - int cnt; - - newnode = query_tree_mutator((Query *) node, - adjust_appendrel_attrs_mutator, - (void *) &context, - QTW_IGNORE_RC_SUBQUERIES); - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (newnode->resultRelation == appinfo->parent_relid) - { - newnode->resultRelation = appinfo->child_relid; - /* Fix tlist resnos too, if it's inherited UPDATE */ - if (newnode->commandType == CMD_UPDATE) - newnode->targetList = - adjust_inherited_tlist(newnode->targetList, - appinfo); - break; - } - } - - result = (Node *) newnode; - } - else - result = adjust_appendrel_attrs_mutator(node, &context); - - return result; -} - -static Node * -adjust_appendrel_attrs_mutator(Node *node, - adjust_appendrel_attrs_context *context) -{ - AppendRelInfo **appinfos = context->appinfos; - int nappinfos = context->nappinfos; - int cnt; - - if (node == NULL) - return NULL; - if (IsA(node, Var)) - { - Var *var = (Var *) copyObject(node); - AppendRelInfo *appinfo = NULL; - - for (cnt = 0; cnt < nappinfos; cnt++) - { - if (var->varno == appinfos[cnt]->parent_relid) - { - appinfo = appinfos[cnt]; - break; - } - } - - if (var->varlevelsup == 0 && appinfo) - { - var->varno = appinfo->child_relid; - var->varnoold = appinfo->child_relid; - if (var->varattno > 0) - { - Node *newnode; - - if (var->varattno > list_length(appinfo->translated_vars)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - var->varattno, get_rel_name(appinfo->parent_reloid)); - newnode = copyObject(list_nth(appinfo->translated_vars, - var->varattno - 1)); - if (newnode == NULL) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - var->varattno, get_rel_name(appinfo->parent_reloid)); - return newnode; - } - else if (var->varattno == 0) - { - /* - * Whole-row Var: if we are dealing with named rowtypes, we - * can use a whole-row Var for the child table plus a coercion - * step to convert the tuple layout to the parent's rowtype. - * Otherwise we have to generate a RowExpr. - */ - if (OidIsValid(appinfo->child_reltype)) - { - Assert(var->vartype == appinfo->parent_reltype); - if (appinfo->parent_reltype != appinfo->child_reltype) - { - ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr); - - r->arg = (Expr *) var; - r->resulttype = appinfo->parent_reltype; - r->convertformat = COERCE_IMPLICIT_CAST; - r->location = -1; - /* Make sure the Var node has the right type ID, too */ - var->vartype = appinfo->child_reltype; - return (Node *) r; - } - } - else - { - /* - * Build a RowExpr containing the translated variables. - * - * In practice var->vartype will always be RECORDOID here, - * so we need to come up with some suitable column names. - * We use the parent RTE's column names. - * - * Note: we can't get here for inheritance cases, so there - * is no need to worry that translated_vars might contain - * some dummy NULLs. - */ - RowExpr *rowexpr; - List *fields; - RangeTblEntry *rte; - - rte = rt_fetch(appinfo->parent_relid, - context->root->parse->rtable); - fields = copyObject(appinfo->translated_vars); - rowexpr = makeNode(RowExpr); - rowexpr->args = fields; - rowexpr->row_typeid = var->vartype; - rowexpr->row_format = COERCE_IMPLICIT_CAST; - rowexpr->colnames = copyObject(rte->eref->colnames); - rowexpr->location = -1; - - return (Node *) rowexpr; - } - } - /* system attributes don't need any other translation */ - } - return (Node *) var; - } - if (IsA(node, CurrentOfExpr)) - { - CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node); - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (cexpr->cvarno == appinfo->parent_relid) - { - cexpr->cvarno = appinfo->child_relid; - break; - } - } - return (Node *) cexpr; - } - if (IsA(node, RangeTblRef)) - { - RangeTblRef *rtr = (RangeTblRef *) copyObject(node); - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - if (rtr->rtindex == appinfo->parent_relid) - { - rtr->rtindex = appinfo->child_relid; - break; - } - } - return (Node *) rtr; - } - if (IsA(node, JoinExpr)) - { - /* Copy the JoinExpr node with correct mutation of subnodes */ - JoinExpr *j; - AppendRelInfo *appinfo; - - j = (JoinExpr *) expression_tree_mutator(node, - adjust_appendrel_attrs_mutator, - (void *) context); - /* now fix JoinExpr's rtindex (probably never happens) */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - appinfo = appinfos[cnt]; - - if (j->rtindex == appinfo->parent_relid) - { - j->rtindex = appinfo->child_relid; - break; - } - } - return (Node *) j; - } - if (IsA(node, PlaceHolderVar)) - { - /* Copy the PlaceHolderVar node with correct mutation of subnodes */ - PlaceHolderVar *phv; - - phv = (PlaceHolderVar *) expression_tree_mutator(node, - adjust_appendrel_attrs_mutator, - (void *) context); - /* now fix PlaceHolderVar's relid sets */ - if (phv->phlevelsup == 0) - phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos, - context->appinfos); - return (Node *) phv; - } - /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, SpecialJoinInfo)); - Assert(!IsA(node, AppendRelInfo)); - Assert(!IsA(node, PlaceHolderInfo)); - Assert(!IsA(node, MinMaxAggInfo)); - - /* - * We have to process RestrictInfo nodes specially. (Note: although - * set_append_rel_pathlist will hide RestrictInfos in the parent's - * baserestrictinfo list from us, it doesn't hide those in joininfo.) - */ - if (IsA(node, RestrictInfo)) - { - RestrictInfo *oldinfo = (RestrictInfo *) node; - RestrictInfo *newinfo = makeNode(RestrictInfo); - - /* Copy all flat-copiable fields */ - memcpy(newinfo, oldinfo, sizeof(RestrictInfo)); - - /* Recursively fix the clause itself */ - newinfo->clause = (Expr *) - adjust_appendrel_attrs_mutator((Node *) oldinfo->clause, context); - - /* and the modified version, if an OR clause */ - newinfo->orclause = (Expr *) - adjust_appendrel_attrs_mutator((Node *) oldinfo->orclause, context); - - /* adjust relid sets too */ - newinfo->clause_relids = adjust_child_relids(oldinfo->clause_relids, - context->nappinfos, - context->appinfos); - newinfo->required_relids = adjust_child_relids(oldinfo->required_relids, - context->nappinfos, - context->appinfos); - newinfo->outer_relids = adjust_child_relids(oldinfo->outer_relids, - context->nappinfos, - context->appinfos); - newinfo->nullable_relids = adjust_child_relids(oldinfo->nullable_relids, - context->nappinfos, - context->appinfos); - newinfo->left_relids = adjust_child_relids(oldinfo->left_relids, - context->nappinfos, - context->appinfos); - newinfo->right_relids = adjust_child_relids(oldinfo->right_relids, - context->nappinfos, - context->appinfos); - - /* - * Reset cached derivative fields, since these might need to have - * different values when considering the child relation. Note we - * don't reset left_ec/right_ec: each child variable is implicitly - * equivalent to its parent, so still a member of the same EC if any. - */ - newinfo->eval_cost.startup = -1; - newinfo->norm_selec = -1; - newinfo->outer_selec = -1; - newinfo->left_em = NULL; - newinfo->right_em = NULL; - newinfo->scansel_cache = NIL; - newinfo->left_bucketsize = -1; - newinfo->right_bucketsize = -1; - newinfo->left_mcvfreq = -1; - newinfo->right_mcvfreq = -1; - - return (Node *) newinfo; - } - - /* - * NOTE: we do not need to recurse into sublinks, because they should - * already have been converted to subplans before we see them. - */ - Assert(!IsA(node, SubLink)); - Assert(!IsA(node, Query)); - - return expression_tree_mutator(node, adjust_appendrel_attrs_mutator, - (void *) context); -} - -/* - * Substitute child relids for parent relids in a Relid set. The array of - * appinfos specifies the substitutions to be performed. - */ -static Relids -adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos) -{ - Bitmapset *result = NULL; - int cnt; - - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - /* Remove parent, add child */ - if (bms_is_member(appinfo->parent_relid, relids)) - { - /* Make a copy if we are changing the set. */ - if (!result) - result = bms_copy(relids); - - result = bms_del_member(result, appinfo->parent_relid); - result = bms_add_member(result, appinfo->child_relid); - } - } - - /* If we made any changes, return the modified copy. */ - if (result) - return result; - - /* Otherwise, return the original set without modification. */ - return relids; -} - -/* - * Replace any relid present in top_parent_relids with its child in - * child_relids. Members of child_relids can be multiple levels below top - * parent in the partition hierarchy. - */ -Relids -adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, - Relids child_relids, Relids top_parent_relids) -{ - AppendRelInfo **appinfos; - int nappinfos; - Relids parent_relids = NULL; - Relids result; - Relids tmp_result = NULL; - int cnt; - - /* - * If the given relids set doesn't contain any of the top parent relids, - * it will remain unchanged. - */ - if (!bms_overlap(relids, top_parent_relids)) - return relids; - - appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); - - /* Construct relids set for the immediate parent of the given child. */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); - } - - /* Recurse if immediate parent is not the top parent. */ - if (!bms_equal(parent_relids, top_parent_relids)) - { - tmp_result = adjust_child_relids_multilevel(root, relids, - parent_relids, - top_parent_relids); - relids = tmp_result; - } - - result = adjust_child_relids(relids, nappinfos, appinfos); - - /* Free memory consumed by any intermediate result. */ - if (tmp_result) - bms_free(tmp_result); - bms_free(parent_relids); - pfree(appinfos); - - return result; -} - -/* - * Adjust the targetlist entries of an inherited UPDATE operation - * - * The expressions have already been fixed, but we have to make sure that - * the target resnos match the child table (they may not, in the case of - * a column that was added after-the-fact by ALTER TABLE). In some cases - * this can force us to re-order the tlist to preserve resno ordering. - * (We do all this work in special cases so that preptlist.c is fast for - * the typical case.) - * - * The given tlist has already been through expression_tree_mutator; - * therefore the TargetEntry nodes are fresh copies that it's okay to - * scribble on. - * - * Note that this is not needed for INSERT because INSERT isn't inheritable. - */ -static List * -adjust_inherited_tlist(List *tlist, AppendRelInfo *context) -{ - bool changed_it = false; - ListCell *tl; - List *new_tlist; - bool more; - int attrno; - - /* This should only happen for an inheritance case, not UNION ALL */ - Assert(OidIsValid(context->parent_reloid)); - - /* Scan tlist and update resnos to match attnums of child rel */ - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - Var *childvar; - - if (tle->resjunk) - continue; /* ignore junk items */ - - /* Look up the translation of this column: it must be a Var */ - if (tle->resno <= 0 || - tle->resno > list_length(context->translated_vars)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - tle->resno, get_rel_name(context->parent_reloid)); - childvar = (Var *) list_nth(context->translated_vars, tle->resno - 1); - if (childvar == NULL || !IsA(childvar, Var)) - elog(ERROR, "attribute %d of relation \"%s\" does not exist", - tle->resno, get_rel_name(context->parent_reloid)); - - if (tle->resno != childvar->varattno) - { - tle->resno = childvar->varattno; - changed_it = true; - } - } - - /* - * If we changed anything, re-sort the tlist by resno, and make sure - * resjunk entries have resnos above the last real resno. The sort - * algorithm is a bit stupid, but for such a seldom-taken path, small is - * probably better than fast. - */ - if (!changed_it) - return tlist; - - new_tlist = NIL; - more = true; - for (attrno = 1; more; attrno++) - { - more = false; - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - - if (tle->resjunk) - continue; /* ignore junk items */ - - if (tle->resno == attrno) - new_tlist = lappend(new_tlist, tle); - else if (tle->resno > attrno) - more = true; - } - } - - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - - if (!tle->resjunk) - continue; /* here, ignore non-junk items */ - - tle->resno = attrno; - new_tlist = lappend(new_tlist, tle); - attrno++; - } - - return new_tlist; -} - -/* - * adjust_appendrel_attrs_multilevel - * Apply Var translations from a toplevel appendrel parent down to a child. - * - * In some cases we need to translate expressions referencing a parent relation - * to reference an appendrel child that's multiple levels removed from it. - */ -Node * -adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, - Relids child_relids, - Relids top_parent_relids) -{ - AppendRelInfo **appinfos; - Bitmapset *parent_relids = NULL; - int nappinfos; - int cnt; - - Assert(bms_num_members(child_relids) == bms_num_members(top_parent_relids)); - - appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); - - /* Construct relids set for the immediate parent of given child. */ - for (cnt = 0; cnt < nappinfos; cnt++) - { - AppendRelInfo *appinfo = appinfos[cnt]; - - parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); - } - - /* Recurse if immediate parent is not the top parent. */ - if (!bms_equal(parent_relids, top_parent_relids)) - node = adjust_appendrel_attrs_multilevel(root, node, parent_relids, - top_parent_relids); - - /* Now translate for this child */ - node = adjust_appendrel_attrs(root, node, nappinfos, appinfos); - - pfree(appinfos); - - return node; -} - -/* - * Construct the SpecialJoinInfo for a child-join by translating - * SpecialJoinInfo for the join between parents. left_relids and right_relids - * are the relids of left and right side of the join respectively. - */ -SpecialJoinInfo * -build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, - Relids left_relids, Relids right_relids) -{ - SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo); - AppendRelInfo **left_appinfos; - int left_nappinfos; - AppendRelInfo **right_appinfos; - int right_nappinfos; - - memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo)); - left_appinfos = find_appinfos_by_relids(root, left_relids, - &left_nappinfos); - right_appinfos = find_appinfos_by_relids(root, right_relids, - &right_nappinfos); - - sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand, - left_nappinfos, left_appinfos); - sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand, - right_nappinfos, - right_appinfos); - sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand, - left_nappinfos, left_appinfos); - sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand, - right_nappinfos, - right_appinfos); - sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root, - (Node *) sjinfo->semi_rhs_exprs, - right_nappinfos, - right_appinfos); - - pfree(left_appinfos); - pfree(right_appinfos); - - return sjinfo; -} - -/* - * find_appinfos_by_relids - * Find AppendRelInfo structures for all relations specified by relids. - * - * The AppendRelInfos are returned in an array, which can be pfree'd by the - * caller. *nappinfos is set to the number of entries in the array. - */ -AppendRelInfo ** -find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos) -{ - AppendRelInfo **appinfos; - int cnt = 0; - int i; - - *nappinfos = bms_num_members(relids); - appinfos = (AppendRelInfo **) palloc(sizeof(AppendRelInfo *) * *nappinfos); - - i = -1; - while ((i = bms_next_member(relids, i)) >= 0) - { - AppendRelInfo *appinfo = root->append_rel_array[i]; - - if (!appinfo) - elog(ERROR, "child rel %d not found in append_rel_array", i); - - appinfos[cnt++] = appinfo; - } - return appinfos; -} diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile index c54d0a690d..adffb01226 100644 --- a/src/backend/optimizer/util/Makefile +++ b/src/backend/optimizer/util/Makefile @@ -12,7 +12,8 @@ subdir = src/backend/optimizer/util top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = clauses.o joininfo.o orclauses.o pathnode.o placeholder.o \ - plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o +OBJS = appendinfo.o clauses.o inherit.o joininfo.o orclauses.o pathnode.o \ + placeholder.o plancat.o predtest.o relnode.o restrictinfo.o tlist.o \ + var.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c new file mode 100644 index 0000000000..01040c5cfe --- /dev/null +++ b/src/backend/optimizer/util/appendinfo.c @@ -0,0 +1,824 @@ +/*------------------------------------------------------------------------- + * + * appendinfo.c + * Routines for mapping between append parent(s) and children + * + * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/optimizer/path/appendinfo.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" +#include "parser/parsetree.h" +#include "utils/rel.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" + +typedef struct +{ + PlannerInfo *root; + int nappinfos; + AppendRelInfo **appinfos; +} adjust_appendrel_attrs_context; + +static void make_inh_translation_list(Relation oldrelation, + Relation newrelation, + Index newvarno, + List **translated_vars); +static Node *adjust_appendrel_attrs_mutator(Node *node, + adjust_appendrel_attrs_context *context); +static Relids adjust_child_relids(Relids relids, int nappinfos, + AppendRelInfo **appinfos); +static List *adjust_inherited_tlist(List *tlist, + AppendRelInfo *context); + +/* + * make_append_rel_info + * Build an AppendRelInfo for the parent-child pair + */ +AppendRelInfo * +make_append_rel_info(Relation parentrel, Relation childrel, + Index parentRTindex, Index childRTindex) +{ + AppendRelInfo *appinfo = makeNode(AppendRelInfo); + + appinfo->parent_relid = parentRTindex; + appinfo->child_relid = childRTindex; + appinfo->parent_reltype = parentrel->rd_rel->reltype; + appinfo->child_reltype = childrel->rd_rel->reltype; + make_inh_translation_list(parentrel, childrel, childRTindex, + &appinfo->translated_vars); + appinfo->parent_reloid = RelationGetRelid(parentrel); + + return appinfo; +} + +/* + * make_inh_translation_list + * Build the list of translations from parent Vars to child Vars for + * an inheritance child. + * + * For paranoia's sake, we match type/collation as well as attribute name. + */ +static void +make_inh_translation_list(Relation oldrelation, Relation newrelation, + Index newvarno, + List **translated_vars) +{ + List *vars = NIL; + TupleDesc old_tupdesc = RelationGetDescr(oldrelation); + TupleDesc new_tupdesc = RelationGetDescr(newrelation); + Oid new_relid = RelationGetRelid(newrelation); + int oldnatts = old_tupdesc->natts; + int newnatts = new_tupdesc->natts; + int old_attno; + int new_attno = 0; + + for (old_attno = 0; old_attno < oldnatts; old_attno++) + { + Form_pg_attribute att; + char *attname; + Oid atttypid; + int32 atttypmod; + Oid attcollation; + + att = TupleDescAttr(old_tupdesc, old_attno); + if (att->attisdropped) + { + /* Just put NULL into this list entry */ + vars = lappend(vars, NULL); + continue; + } + attname = NameStr(att->attname); + atttypid = att->atttypid; + atttypmod = att->atttypmod; + attcollation = att->attcollation; + + /* + * When we are generating the "translation list" for the parent table + * of an inheritance set, no need to search for matches. + */ + if (oldrelation == newrelation) + { + vars = lappend(vars, makeVar(newvarno, + (AttrNumber) (old_attno + 1), + atttypid, + atttypmod, + attcollation, + 0)); + continue; + } + + /* + * Otherwise we have to search for the matching column by name. + * There's no guarantee it'll have the same column position, because + * of cases like ALTER TABLE ADD COLUMN and multiple inheritance. + * However, in simple cases, the relative order of columns is mostly + * the same in both relations, so try the column of newrelation that + * follows immediately after the one that we just found, and if that + * fails, let syscache handle it. + */ + if (new_attno >= newnatts || + (att = TupleDescAttr(new_tupdesc, new_attno))->attisdropped || + strcmp(attname, NameStr(att->attname)) != 0) + { + HeapTuple newtup; + + newtup = SearchSysCacheAttName(new_relid, attname); + if (!newtup) + elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", + attname, RelationGetRelationName(newrelation)); + new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; + ReleaseSysCache(newtup); + + att = TupleDescAttr(new_tupdesc, new_attno); + } + + /* Found it, check type and collation match */ + if (atttypid != att->atttypid || atttypmod != att->atttypmod) + elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", + attname, RelationGetRelationName(newrelation)); + if (attcollation != att->attcollation) + elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", + attname, RelationGetRelationName(newrelation)); + + vars = lappend(vars, makeVar(newvarno, + (AttrNumber) (new_attno + 1), + atttypid, + atttypmod, + attcollation, + 0)); + new_attno++; + } + + *translated_vars = vars; +} + +/* + * translate_col_privs + * Translate a bitmapset representing per-column privileges from the + * parent rel's attribute numbering to the child's. + * + * The only surprise here is that we don't translate a parent whole-row + * reference into a child whole-row reference. That would mean requiring + * permissions on all child columns, which is overly strict, since the + * query is really only going to reference the inherited columns. Instead + * we set the per-column bits for all inherited columns. + */ +Bitmapset * +translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars) +{ + Bitmapset *child_privs = NULL; + bool whole_row; + int attno; + ListCell *lc; + + /* System attributes have the same numbers in all tables */ + for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) + { + if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + attno - FirstLowInvalidHeapAttributeNumber); + } + + /* Check if parent has whole-row reference */ + whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, + parent_privs); + + /* And now translate the regular user attributes, using the vars list */ + attno = InvalidAttrNumber; + foreach(lc, translated_vars) + { + Var *var = lfirst_node(Var, lc); + + attno++; + if (var == NULL) /* ignore dropped columns */ + continue; + if (whole_row || + bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + var->varattno - FirstLowInvalidHeapAttributeNumber); + } + + return child_privs; +} + +/* + * adjust_appendrel_attrs + * Copy the specified query or expression and translate Vars referring to a + * parent rel to refer to the corresponding child rel instead. We also + * update rtindexes appearing outside Vars, such as resultRelation and + * jointree relids. + * + * Note: this is only applied after conversion of sublinks to subplans, + * so we don't need to cope with recursion into sub-queries. + * + * Note: this is not hugely different from what pullup_replace_vars() does; + * maybe we should try to fold the two routines together. + */ +Node * +adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, + AppendRelInfo **appinfos) +{ + Node *result; + adjust_appendrel_attrs_context context; + + context.root = root; + context.nappinfos = nappinfos; + context.appinfos = appinfos; + + /* If there's nothing to adjust, don't call this function. */ + Assert(nappinfos >= 1 && appinfos != NULL); + + /* + * Must be prepared to start with a Query or a bare expression tree. + */ + if (node && IsA(node, Query)) + { + Query *newnode; + int cnt; + + newnode = query_tree_mutator((Query *) node, + adjust_appendrel_attrs_mutator, + (void *) &context, + QTW_IGNORE_RC_SUBQUERIES); + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (newnode->resultRelation == appinfo->parent_relid) + { + newnode->resultRelation = appinfo->child_relid; + /* Fix tlist resnos too, if it's inherited UPDATE */ + if (newnode->commandType == CMD_UPDATE) + newnode->targetList = + adjust_inherited_tlist(newnode->targetList, + appinfo); + break; + } + } + + result = (Node *) newnode; + } + else + result = adjust_appendrel_attrs_mutator(node, &context); + + return result; +} + +static Node * +adjust_appendrel_attrs_mutator(Node *node, + adjust_appendrel_attrs_context *context) +{ + AppendRelInfo **appinfos = context->appinfos; + int nappinfos = context->nappinfos; + int cnt; + + if (node == NULL) + return NULL; + if (IsA(node, Var)) + { + Var *var = (Var *) copyObject(node); + AppendRelInfo *appinfo = NULL; + + for (cnt = 0; cnt < nappinfos; cnt++) + { + if (var->varno == appinfos[cnt]->parent_relid) + { + appinfo = appinfos[cnt]; + break; + } + } + + if (var->varlevelsup == 0 && appinfo) + { + var->varno = appinfo->child_relid; + var->varnoold = appinfo->child_relid; + if (var->varattno > 0) + { + Node *newnode; + + if (var->varattno > list_length(appinfo->translated_vars)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + var->varattno, get_rel_name(appinfo->parent_reloid)); + newnode = copyObject(list_nth(appinfo->translated_vars, + var->varattno - 1)); + if (newnode == NULL) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + var->varattno, get_rel_name(appinfo->parent_reloid)); + return newnode; + } + else if (var->varattno == 0) + { + /* + * Whole-row Var: if we are dealing with named rowtypes, we + * can use a whole-row Var for the child table plus a coercion + * step to convert the tuple layout to the parent's rowtype. + * Otherwise we have to generate a RowExpr. + */ + if (OidIsValid(appinfo->child_reltype)) + { + Assert(var->vartype == appinfo->parent_reltype); + if (appinfo->parent_reltype != appinfo->child_reltype) + { + ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr); + + r->arg = (Expr *) var; + r->resulttype = appinfo->parent_reltype; + r->convertformat = COERCE_IMPLICIT_CAST; + r->location = -1; + /* Make sure the Var node has the right type ID, too */ + var->vartype = appinfo->child_reltype; + return (Node *) r; + } + } + else + { + /* + * Build a RowExpr containing the translated variables. + * + * In practice var->vartype will always be RECORDOID here, + * so we need to come up with some suitable column names. + * We use the parent RTE's column names. + * + * Note: we can't get here for inheritance cases, so there + * is no need to worry that translated_vars might contain + * some dummy NULLs. + */ + RowExpr *rowexpr; + List *fields; + RangeTblEntry *rte; + + rte = rt_fetch(appinfo->parent_relid, + context->root->parse->rtable); + fields = copyObject(appinfo->translated_vars); + rowexpr = makeNode(RowExpr); + rowexpr->args = fields; + rowexpr->row_typeid = var->vartype; + rowexpr->row_format = COERCE_IMPLICIT_CAST; + rowexpr->colnames = copyObject(rte->eref->colnames); + rowexpr->location = -1; + + return (Node *) rowexpr; + } + } + /* system attributes don't need any other translation */ + } + return (Node *) var; + } + if (IsA(node, CurrentOfExpr)) + { + CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node); + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (cexpr->cvarno == appinfo->parent_relid) + { + cexpr->cvarno = appinfo->child_relid; + break; + } + } + return (Node *) cexpr; + } + if (IsA(node, RangeTblRef)) + { + RangeTblRef *rtr = (RangeTblRef *) copyObject(node); + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + if (rtr->rtindex == appinfo->parent_relid) + { + rtr->rtindex = appinfo->child_relid; + break; + } + } + return (Node *) rtr; + } + if (IsA(node, JoinExpr)) + { + /* Copy the JoinExpr node with correct mutation of subnodes */ + JoinExpr *j; + AppendRelInfo *appinfo; + + j = (JoinExpr *) expression_tree_mutator(node, + adjust_appendrel_attrs_mutator, + (void *) context); + /* now fix JoinExpr's rtindex (probably never happens) */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + appinfo = appinfos[cnt]; + + if (j->rtindex == appinfo->parent_relid) + { + j->rtindex = appinfo->child_relid; + break; + } + } + return (Node *) j; + } + if (IsA(node, PlaceHolderVar)) + { + /* Copy the PlaceHolderVar node with correct mutation of subnodes */ + PlaceHolderVar *phv; + + phv = (PlaceHolderVar *) expression_tree_mutator(node, + adjust_appendrel_attrs_mutator, + (void *) context); + /* now fix PlaceHolderVar's relid sets */ + if (phv->phlevelsup == 0) + phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos, + context->appinfos); + return (Node *) phv; + } + /* Shouldn't need to handle planner auxiliary nodes here */ + Assert(!IsA(node, SpecialJoinInfo)); + Assert(!IsA(node, AppendRelInfo)); + Assert(!IsA(node, PlaceHolderInfo)); + Assert(!IsA(node, MinMaxAggInfo)); + + /* + * We have to process RestrictInfo nodes specially. (Note: although + * set_append_rel_pathlist will hide RestrictInfos in the parent's + * baserestrictinfo list from us, it doesn't hide those in joininfo.) + */ + if (IsA(node, RestrictInfo)) + { + RestrictInfo *oldinfo = (RestrictInfo *) node; + RestrictInfo *newinfo = makeNode(RestrictInfo); + + /* Copy all flat-copiable fields */ + memcpy(newinfo, oldinfo, sizeof(RestrictInfo)); + + /* Recursively fix the clause itself */ + newinfo->clause = (Expr *) + adjust_appendrel_attrs_mutator((Node *) oldinfo->clause, context); + + /* and the modified version, if an OR clause */ + newinfo->orclause = (Expr *) + adjust_appendrel_attrs_mutator((Node *) oldinfo->orclause, context); + + /* adjust relid sets too */ + newinfo->clause_relids = adjust_child_relids(oldinfo->clause_relids, + context->nappinfos, + context->appinfos); + newinfo->required_relids = adjust_child_relids(oldinfo->required_relids, + context->nappinfos, + context->appinfos); + newinfo->outer_relids = adjust_child_relids(oldinfo->outer_relids, + context->nappinfos, + context->appinfos); + newinfo->nullable_relids = adjust_child_relids(oldinfo->nullable_relids, + context->nappinfos, + context->appinfos); + newinfo->left_relids = adjust_child_relids(oldinfo->left_relids, + context->nappinfos, + context->appinfos); + newinfo->right_relids = adjust_child_relids(oldinfo->right_relids, + context->nappinfos, + context->appinfos); + + /* + * Reset cached derivative fields, since these might need to have + * different values when considering the child relation. Note we + * don't reset left_ec/right_ec: each child variable is implicitly + * equivalent to its parent, so still a member of the same EC if any. + */ + newinfo->eval_cost.startup = -1; + newinfo->norm_selec = -1; + newinfo->outer_selec = -1; + newinfo->left_em = NULL; + newinfo->right_em = NULL; + newinfo->scansel_cache = NIL; + newinfo->left_bucketsize = -1; + newinfo->right_bucketsize = -1; + newinfo->left_mcvfreq = -1; + newinfo->right_mcvfreq = -1; + + return (Node *) newinfo; + } + + /* + * NOTE: we do not need to recurse into sublinks, because they should + * already have been converted to subplans before we see them. + */ + Assert(!IsA(node, SubLink)); + Assert(!IsA(node, Query)); + + return expression_tree_mutator(node, adjust_appendrel_attrs_mutator, + (void *) context); +} + +/* + * Substitute child relids for parent relids in a Relid set. The array of + * appinfos specifies the substitutions to be performed. + */ +static Relids +adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos) +{ + Bitmapset *result = NULL; + int cnt; + + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + /* Remove parent, add child */ + if (bms_is_member(appinfo->parent_relid, relids)) + { + /* Make a copy if we are changing the set. */ + if (!result) + result = bms_copy(relids); + + result = bms_del_member(result, appinfo->parent_relid); + result = bms_add_member(result, appinfo->child_relid); + } + } + + /* If we made any changes, return the modified copy. */ + if (result) + return result; + + /* Otherwise, return the original set without modification. */ + return relids; +} + +/* + * Replace any relid present in top_parent_relids with its child in + * child_relids. Members of child_relids can be multiple levels below top + * parent in the partition hierarchy. + */ +Relids +adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, + Relids child_relids, Relids top_parent_relids) +{ + AppendRelInfo **appinfos; + int nappinfos; + Relids parent_relids = NULL; + Relids result; + Relids tmp_result = NULL; + int cnt; + + /* + * If the given relids set doesn't contain any of the top parent relids, + * it will remain unchanged. + */ + if (!bms_overlap(relids, top_parent_relids)) + return relids; + + appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); + + /* Construct relids set for the immediate parent of the given child. */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); + } + + /* Recurse if immediate parent is not the top parent. */ + if (!bms_equal(parent_relids, top_parent_relids)) + { + tmp_result = adjust_child_relids_multilevel(root, relids, + parent_relids, + top_parent_relids); + relids = tmp_result; + } + + result = adjust_child_relids(relids, nappinfos, appinfos); + + /* Free memory consumed by any intermediate result. */ + if (tmp_result) + bms_free(tmp_result); + bms_free(parent_relids); + pfree(appinfos); + + return result; +} + +/* + * Adjust the targetlist entries of an inherited UPDATE operation + * + * The expressions have already been fixed, but we have to make sure that + * the target resnos match the child table (they may not, in the case of + * a column that was added after-the-fact by ALTER TABLE). In some cases + * this can force us to re-order the tlist to preserve resno ordering. + * (We do all this work in special cases so that preptlist.c is fast for + * the typical case.) + * + * The given tlist has already been through expression_tree_mutator; + * therefore the TargetEntry nodes are fresh copies that it's okay to + * scribble on. + * + * Note that this is not needed for INSERT because INSERT isn't inheritable. + */ +static List * +adjust_inherited_tlist(List *tlist, AppendRelInfo *context) +{ + bool changed_it = false; + ListCell *tl; + List *new_tlist; + bool more; + int attrno; + + /* This should only happen for an inheritance case, not UNION ALL */ + Assert(OidIsValid(context->parent_reloid)); + + /* Scan tlist and update resnos to match attnums of child rel */ + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + Var *childvar; + + if (tle->resjunk) + continue; /* ignore junk items */ + + /* Look up the translation of this column: it must be a Var */ + if (tle->resno <= 0 || + tle->resno > list_length(context->translated_vars)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + tle->resno, get_rel_name(context->parent_reloid)); + childvar = (Var *) list_nth(context->translated_vars, tle->resno - 1); + if (childvar == NULL || !IsA(childvar, Var)) + elog(ERROR, "attribute %d of relation \"%s\" does not exist", + tle->resno, get_rel_name(context->parent_reloid)); + + if (tle->resno != childvar->varattno) + { + tle->resno = childvar->varattno; + changed_it = true; + } + } + + /* + * If we changed anything, re-sort the tlist by resno, and make sure + * resjunk entries have resnos above the last real resno. The sort + * algorithm is a bit stupid, but for such a seldom-taken path, small is + * probably better than fast. + */ + if (!changed_it) + return tlist; + + new_tlist = NIL; + more = true; + for (attrno = 1; more; attrno++) + { + more = false; + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + + if (tle->resjunk) + continue; /* ignore junk items */ + + if (tle->resno == attrno) + new_tlist = lappend(new_tlist, tle); + else if (tle->resno > attrno) + more = true; + } + } + + foreach(tl, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + + if (!tle->resjunk) + continue; /* here, ignore non-junk items */ + + tle->resno = attrno; + new_tlist = lappend(new_tlist, tle); + attrno++; + } + + return new_tlist; +} + +/* + * adjust_appendrel_attrs_multilevel + * Apply Var translations from a toplevel appendrel parent down to a child. + * + * In some cases we need to translate expressions referencing a parent relation + * to reference an appendrel child that's multiple levels removed from it. + */ +Node * +adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, + Relids child_relids, + Relids top_parent_relids) +{ + AppendRelInfo **appinfos; + Bitmapset *parent_relids = NULL; + int nappinfos; + int cnt; + + Assert(bms_num_members(child_relids) == bms_num_members(top_parent_relids)); + + appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos); + + /* Construct relids set for the immediate parent of given child. */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + AppendRelInfo *appinfo = appinfos[cnt]; + + parent_relids = bms_add_member(parent_relids, appinfo->parent_relid); + } + + /* Recurse if immediate parent is not the top parent. */ + if (!bms_equal(parent_relids, top_parent_relids)) + node = adjust_appendrel_attrs_multilevel(root, node, parent_relids, + top_parent_relids); + + /* Now translate for this child */ + node = adjust_appendrel_attrs(root, node, nappinfos, appinfos); + + pfree(appinfos); + + return node; +} + +/* + * Construct the SpecialJoinInfo for a child-join by translating + * SpecialJoinInfo for the join between parents. left_relids and right_relids + * are the relids of left and right side of the join respectively. + */ +SpecialJoinInfo * +build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, + Relids left_relids, Relids right_relids) +{ + SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo); + AppendRelInfo **left_appinfos; + int left_nappinfos; + AppendRelInfo **right_appinfos; + int right_nappinfos; + + memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo)); + left_appinfos = find_appinfos_by_relids(root, left_relids, + &left_nappinfos); + right_appinfos = find_appinfos_by_relids(root, right_relids, + &right_nappinfos); + + sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand, + left_nappinfos, left_appinfos); + sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand, + right_nappinfos, + right_appinfos); + sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand, + left_nappinfos, left_appinfos); + sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand, + right_nappinfos, + right_appinfos); + sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root, + (Node *) sjinfo->semi_rhs_exprs, + right_nappinfos, + right_appinfos); + + pfree(left_appinfos); + pfree(right_appinfos); + + return sjinfo; +} + +/* + * find_appinfos_by_relids + * Find AppendRelInfo structures for all relations specified by relids. + * + * The AppendRelInfos are returned in an array, which can be pfree'd by the + * caller. *nappinfos is set to the number of entries in the array. + */ +AppendRelInfo ** +find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos) +{ + AppendRelInfo **appinfos; + int cnt = 0; + int i; + + *nappinfos = bms_num_members(relids); + appinfos = (AppendRelInfo **) palloc(sizeof(AppendRelInfo *) * *nappinfos); + + i = -1; + while ((i = bms_next_member(relids, i)) >= 0) + { + AppendRelInfo *appinfo = root->append_rel_array[i]; + + if (!appinfo) + elog(ERROR, "child rel %d not found in append_rel_array", i); + + appinfos[cnt++] = appinfo; + } + return appinfos; +} diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c new file mode 100644 index 0000000000..243d61da8f --- /dev/null +++ b/src/backend/optimizer/util/inherit.c @@ -0,0 +1,451 @@ +/*------------------------------------------------------------------------- + * + * inherit.c + * Routines to process child relations in inheritance trees + * + * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/optimizer/path/inherit.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <limits.h> + +#include "access/sysattr.h" +#include "catalog/partition.h" +#include "catalog/pg_class.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_type.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" +#include "optimizer/inherit.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" +#include "optimizer/plancat.h" +#include "optimizer/planmain.h" +#include "optimizer/planner.h" +#include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" +#include "optimizer/tlist.h" +#include "optimizer/var.h" +#include "partitioning/partprune.h" +#include "utils/rel.h" + +static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, + Index rti); +static void expand_partitioned_rtentry(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, Relation parentrel, + PlanRowMark *top_parentrc, LOCKMODE lockmode, + List **appinfos); +static void expand_single_inheritance_child(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, Relation parentrel, + PlanRowMark *top_parentrc, Relation childrel, + List **appinfos, RangeTblEntry **childrte_p, + Index *childRTindex_p); + +/* + * expand_inherited_tables + * Expand each rangetable entry that represents an inheritance set + * into an "append relation". At the conclusion of this process, + * the "inh" flag is set in all and only those RTEs that are append + * relation parents. + */ +void +expand_inherited_tables(PlannerInfo *root) +{ + Index nrtes; + Index rti; + ListCell *rl; + + /* + * expand_inherited_rtentry may add RTEs to parse->rtable. The function is + * expected to recursively handle any RTEs that it creates with inh=true. + * So just scan as far as the original end of the rtable list. + */ + nrtes = list_length(root->parse->rtable); + rl = list_head(root->parse->rtable); + for (rti = 1; rti <= nrtes; rti++) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); + + expand_inherited_rtentry(root, rte, rti); + rl = lnext(rl); + } +} + +/* + * expand_inherited_rtentry + * Check whether a rangetable entry represents an inheritance set. + * If so, add entries for all the child tables to the query's + * rangetable, and build AppendRelInfo nodes for all the child tables + * and add them to root->append_rel_list. If not, clear the entry's + * "inh" flag to prevent later code from looking for AppendRelInfos. + * + * Note that the original RTE is considered to represent the whole + * inheritance set. The first of the generated RTEs is an RTE for the same + * table, but with inh = false, to represent the parent table in its role + * as a simple member of the inheritance set. + * + * A childless table is never considered to be an inheritance set. For + * regular inheritance, a parent RTE must always have at least two associated + * AppendRelInfos: one corresponding to the parent table as a simple member of + * inheritance set and one or more corresponding to the actual children. + * Since a partitioned table is not scanned, it might have only one associated + * AppendRelInfo. + */ +static void +expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +{ + Oid parentOID; + PlanRowMark *oldrc; + Relation oldrelation; + LOCKMODE lockmode; + List *inhOIDs; + ListCell *l; + + /* Does RT entry allow inheritance? */ + if (!rte->inh) + return; + /* Ignore any already-expanded UNION ALL nodes */ + if (rte->rtekind != RTE_RELATION) + { + Assert(rte->rtekind == RTE_SUBQUERY); + return; + } + /* Fast path for common case of childless table */ + parentOID = rte->relid; + if (!has_subclass(parentOID)) + { + /* Clear flag before returning */ + rte->inh = false; + return; + } + + /* + * The rewriter should already have obtained an appropriate lock on each + * relation named in the query. However, for each child relation we add + * to the query, we must obtain an appropriate lock, because this will be + * the first use of those relations in the parse/rewrite/plan pipeline. + * Child rels should use the same lockmode as their parent. + */ + lockmode = rte->rellockmode; + + /* Scan for all members of inheritance set, acquire needed locks */ + inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); + + /* + * Check that there's at least one descendant, else treat as no-child + * case. This could happen despite above has_subclass() check, if table + * once had a child but no longer does. + */ + if (list_length(inhOIDs) < 2) + { + /* Clear flag before returning */ + rte->inh = false; + return; + } + + /* + * If parent relation is selected FOR UPDATE/SHARE, we need to mark its + * PlanRowMark as isParent = true, and generate a new PlanRowMark for each + * child. + */ + oldrc = get_plan_rowmark(root->rowMarks, rti); + if (oldrc) + oldrc->isParent = true; + + /* + * Must open the parent relation to examine its tupdesc. We need not lock + * it; we assume the rewriter already did. + */ + oldrelation = heap_open(parentOID, NoLock); + + /* Scan the inheritance set and expand it */ + if (RelationGetPartitionDesc(oldrelation) != NULL) + { + Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); + + /* + * If this table has partitions, recursively expand them in the order + * in which they appear in the PartitionDesc. While at it, also + * extract the partition key columns of all the partitioned tables. + */ + expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, + lockmode, &root->append_rel_list); + } + else + { + List *appinfos = NIL; + RangeTblEntry *childrte; + Index childRTindex; + + /* + * This table has no partitions. Expand any plain inheritance + * children in the order the OIDs were returned by + * find_all_inheritors. + */ + foreach(l, inhOIDs) + { + Oid childOID = lfirst_oid(l); + Relation newrelation; + + /* Open rel if needed; we already have required locks */ + if (childOID != parentOID) + newrelation = heap_open(childOID, NoLock); + else + newrelation = oldrelation; + + /* + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. + */ + if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) + { + heap_close(newrelation, lockmode); + continue; + } + + expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, + newrelation, + &appinfos, &childrte, + &childRTindex); + + /* Close child relations, but keep locks */ + if (childOID != parentOID) + heap_close(newrelation, NoLock); + } + + /* + * If all the children were temp tables, pretend it's a + * non-inheritance situation; we don't need Append node in that case. + * The duplicate RTE we added for the parent table is harmless, so we + * don't bother to get rid of it; ditto for the useless PlanRowMark + * node. + */ + if (list_length(appinfos) < 2) + rte->inh = false; + else + root->append_rel_list = list_concat(root->append_rel_list, + appinfos); + + } + + heap_close(oldrelation, NoLock); +} + +/* + * expand_partitioned_rtentry + * Recursively expand an RTE for a partitioned table. + */ +static void +expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, Relation parentrel, + PlanRowMark *top_parentrc, LOCKMODE lockmode, + List **appinfos) +{ + int i; + RangeTblEntry *childrte; + Index childRTindex; + PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); + + check_stack_depth(); + + /* A partitioned table should always have a partition descriptor. */ + Assert(partdesc); + + Assert(parentrte->inh); + + /* + * Note down whether any partition key cols are being updated. Though it's + * the root partitioned table's updatedCols we are interested in, we + * instead use parentrte to get the updatedCols. This is convenient + * because parentrte already has the root partrel's updatedCols translated + * to match the attribute ordering of parentrel. + */ + if (!root->partColsUpdated) + root->partColsUpdated = + has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + + /* First expand the partitioned table itself. */ + expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, + top_parentrc, parentrel, + appinfos, &childrte, &childRTindex); + + /* + * If the partitioned table has no partitions, treat this as the + * non-inheritance case. + */ + if (partdesc->nparts == 0) + { + parentrte->inh = false; + return; + } + + for (i = 0; i < partdesc->nparts; i++) + { + Oid childOID = partdesc->oids[i]; + Relation childrel; + + /* Open rel; we already have required locks */ + childrel = heap_open(childOID, NoLock); + + /* + * Temporary partitions belonging to other sessions should have been + * disallowed at definition, but for paranoia's sake, let's double + * check. + */ + if (RELATION_IS_OTHER_TEMP(childrel)) + elog(ERROR, "temporary relation from another session found as partition"); + + expand_single_inheritance_child(root, parentrte, parentRTindex, + parentrel, top_parentrc, childrel, + appinfos, &childrte, &childRTindex); + + /* If this child is itself partitioned, recurse */ + if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, childrte, childRTindex, + childrel, top_parentrc, lockmode, + appinfos); + + /* Close child relation, but keep locks */ + heap_close(childrel, NoLock); + } +} + +/* + * expand_single_inheritance_child + * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus + * maybe a PlanRowMark. + * + * We now expand the partition hierarchy level by level, creating a + * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each + * partitioned descendant acts as a parent of its immediate partitions. + * (This is a difference from what older versions of PostgreSQL did and what + * is still done in the case of table inheritance for unpartitioned tables, + * where the hierarchy is flattened during RTE expansion.) + * + * PlanRowMarks still carry the top-parent's RTI, and the top-parent's + * allMarkTypes field still accumulates values from all descendents. + * + * "parentrte" and "parentRTindex" are immediate parent's RTE and + * RTI. "top_parentrc" is top parent's PlanRowMark. + * + * The child RangeTblEntry and its RTI are returned in "childrte_p" and + * "childRTindex_p" resp. + */ +static void +expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, Relation parentrel, + PlanRowMark *top_parentrc, Relation childrel, + List **appinfos, RangeTblEntry **childrte_p, + Index *childRTindex_p) +{ + Query *parse = root->parse; + Oid parentOID = RelationGetRelid(parentrel); + Oid childOID = RelationGetRelid(childrel); + RangeTblEntry *childrte; + Index childRTindex; + AppendRelInfo *appinfo; + + /* + * Build an RTE for the child, and attach to query's rangetable list. We + * copy most fields of the parent's RTE, but replace relation OID and + * relkind, and set inh = false. Also, set requiredPerms to zero since + * all required permissions checks are done on the original RTE. Likewise, + * set the child's securityQuals to empty, because we only want to apply + * the parent's RLS conditions regardless of what RLS properties + * individual children may have. (This is an intentional choice to make + * inherited RLS work like regular permissions checks.) The parent + * securityQuals will be propagated to children along with other base + * restriction clauses, so we don't need to do it here. + */ + childrte = copyObject(parentrte); + *childrte_p = childrte; + childrte->relid = childOID; + childrte->relkind = childrel->rd_rel->relkind; + /* A partitioned child will need to be expanded further. */ + if (childOID != parentOID && + childrte->relkind == RELKIND_PARTITIONED_TABLE) + childrte->inh = true; + else + childrte->inh = false; + childrte->requiredPerms = 0; + childrte->securityQuals = NIL; + parse->rtable = lappend(parse->rtable, childrte); + childRTindex = list_length(parse->rtable); + *childRTindex_p = childRTindex; + + /* + * We need an AppendRelInfo if paths will be built for the child RTE. If + * childrte->inh is true, then we'll always need to generate append paths + * for it. If childrte->inh is false, we must scan it if it's not a + * partitioned table; but if it is a partitioned table, then it never has + * any data of its own and need not be scanned. + */ + if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) + { + appinfo = make_append_rel_info(parentrel, childrel, + parentRTindex, childRTindex); + *appinfos = lappend(*appinfos, appinfo); + + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childOID != parentOID) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); + } + } + + /* + * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE. + */ + if (top_parentrc) + { + PlanRowMark *childrc = makeNode(PlanRowMark); + + childrc->rti = childRTindex; + childrc->prti = top_parentrc->rti; + childrc->rowmarkId = top_parentrc->rowmarkId; + /* Reselect rowmark type, because relkind might not match parent */ + childrc->markType = select_rowmark_type(childrte, + top_parentrc->strength); + childrc->allMarkTypes = (1 << childrc->markType); + childrc->strength = top_parentrc->strength; + childrc->waitPolicy = top_parentrc->waitPolicy; + + /* + * We mark RowMarks for partitioned child tables as parent RowMarks so + * that the executor ignores them (except their existence means that + * the child tables be locked using appropriate mode). + */ + childrc->isParent = (childrte->relkind == RELKIND_PARTITIONED_TABLE); + + /* Include child's rowmark type in top parent's allMarkTypes */ + top_parentrc->allMarkTypes |= childrc->allMarkTypes; + + root->rowMarks = lappend(root->rowMarks, childrc); + } +} diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 5921e893c1..b2637d0e89 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -20,6 +20,7 @@ #include "foreign/fdwapi.h" #include "nodes/extensible.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 111b42d654..fe83ec4519 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -17,6 +17,7 @@ #include <limits.h> #include "miscadmin.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 0684083a8d..901433c68c 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -44,6 +44,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/pathnode.h" #include "optimizer/planner.h" diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h new file mode 100644 index 0000000000..2348c9c13a --- /dev/null +++ b/src/include/optimizer/appendinfo.h @@ -0,0 +1,42 @@ +/*------------------------------------------------------------------------- + * + * appendinfo.h + * Routines for mapping expressions between append rel parent(s) and + * children + * + * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/optimizer/appendinfo.h + * + *------------------------------------------------------------------------- + */ +#ifndef APPENDINFO_H +#define APPENDINFO_H + +#include "nodes/plannodes.h" +#include "nodes/relation.h" +#include "utils/relcache.h" + +extern AppendRelInfo *make_append_rel_info(Relation parentrel, + Relation childrel, + Index parentRTindex, Index childRTindex); +extern Bitmapset *translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars); +extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, + int nappinfos, AppendRelInfo **appinfos); + +extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, + Relids child_relids, + Relids top_parent_relids); + +extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root, + Relids relids, int *nappinfos); + +extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, + SpecialJoinInfo *parent_sjinfo, + Relids left_relids, Relids right_relids); +extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, + Relids child_relids, Relids top_parent_relids); + +#endif /* APPENDINFO_H */ diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h new file mode 100644 index 0000000000..c0bd54d9ca --- /dev/null +++ b/src/include/optimizer/inherit.h @@ -0,0 +1,25 @@ +/*------------------------------------------------------------------------- + * + * inherit.h + * prototypes for inherit.c. + * + * + * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/optimizer/inherit.h + * + *------------------------------------------------------------------------- + */ +#ifndef INHERIT_H +#define INHERIT_H + +#include "nodes/relation.h" + +/* + * inherit.c + * utilities for dealing with append relations + */ +extern void expand_inherited_tables(PlannerInfo *root); + +#endif /* INHERIT_H */ diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index d6991d5690..62d45dd142 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -47,22 +47,4 @@ extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); */ extern RelOptInfo *plan_set_operations(PlannerInfo *root); -extern void expand_inherited_tables(PlannerInfo *root); - -extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, - int nappinfos, AppendRelInfo **appinfos); - -extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, - Relids child_relids, - Relids top_parent_relids); - -extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root, - Relids relids, int *nappinfos); - -extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, - SpecialJoinInfo *parent_sjinfo, - Relids left_relids, Relids right_relids); -extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, - Relids child_relids, Relids top_parent_relids); - #endif /* PREP_H */ -- 2.11.0
v12-0002-Overhaul-inheritance-update-delete-planning.patch (text/plain, 71.2K) download From 5ef4e04a6196302bf58d9dd0643421e551babdbc Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 11:18:12 +0900 Subject: [PATCH v12 2/6] Overhaul inheritance update/delete planning With the current method, inheritance_planner, which handles the planning for update/delete commands targeting inheritance trees, performs the *whole* planning for each child table. That involves translating the query tree to set a given child table in place of the original inheritance root table as the query's target table, followed by calling grouping_planner on the translated query, which performs query planning and additional steps needed to apply correct target list based on the child target table. That's inefficient for two reasons: 1. partprune.c cannot be used to perform partition pruning, because it can only be invoked from query_planner. With the current approach, query_planner only sees the individual partitions in the query tree it receives, not the parent partitioned table. This leaves each partition to be pruned using constraint exclusion. (constraint exclusion cannon prune hash partitions which means UPDATE/DELETE don't support hash partition pruning.) 2. Repeated invocation of query_planner results in allocating large amounts of memory, especially if there are many child tables. Also, it involves repeatedly performing the same processing, such as jointree processing. This commit addresses both of the issues by rewriting portions of inheritance_planner and adding some supporting functionality to allpath.c. With the new implementation, inheritance_planner calls query_planner only once at the beginning with the original unmodified query, which creates the access paths of individual relations after pruning any unnecessary partitions. For each target child relation, it also creates a sub-PartitionInfo containing translated version of the query and a targetlist suitable for the child. Child PlannerInfos are saved in the top PlannerInfo for using later. If the query involves join against the target relation, join paths are created for each target child relation by replacing the original target table in the join tree by a given child table. Join relations (RelOptInfos thereof) for all target child relations are collected in a global list in the top PlannerInfo. After creating the join paths for all target child relations, inheritance_planner calls grouping_planner() on each child join relation using the previously created child PlannerInfo to finish up the paths such that they produce query's top-level target list expanded according to a given child relation's descriptor. grouping_planner()'s interface is modified so that we can pass it what's called a 'planned_rel', a RelOptInfo that already contains the necessary paths needed to produce its output. This removes some existing code in inheritance_planner that dealt with any subquery RTEs in the query. The rationale of that code was that the subquery RTEs may change during each iteration of planning (that is, for different children), so different iterations better use different copies of those RTEs. That was handled by making fresh copies of those RTEs for each iteration of planning which were appended to the range table, accompanied by modifying all expressions and auxiliary structures that referenced the original subquery RTEs to instead reference the copies (that is, change the varnos). This copying meant we would end up adding S * N new entries to the original range table by the time we got to the last unpruned child, where S is the number of subquery RTEs in the original query and N the number of unpruned children. Since with the new code we perform planning just once, I think we don't need this special handling. Actually, there is a regression test output change due to no longer having copies of subquery RTEs (see the diff of partition_join.out file.) Also we no longer need the hack in relation_excluded_by_constraints() involving setting PlannerInfo.inhTargetKind to enable constraint exclusion for target child relation, which is no longer needed. Constraint exclusion now runs during query_planner step described above. Regression test output change in partition_join.out is due to the fact that we no longer create duplicates of subquery RTEs in child range tables. --- doc/src/sgml/ddl.sgml | 14 +- src/backend/nodes/outfuncs.c | 1 - src/backend/optimizer/path/allpaths.c | 602 +++++++++++++++++++++++++-- src/backend/optimizer/path/equivclass.c | 27 +- src/backend/optimizer/plan/createplan.c | 10 - src/backend/optimizer/plan/planmain.c | 166 +++++++- src/backend/optimizer/plan/planner.c | 434 +++++-------------- src/backend/optimizer/prep/prepjointree.c | 1 - src/backend/optimizer/util/appendinfo.c | 33 +- src/backend/optimizer/util/plancat.c | 50 +-- src/include/nodes/relation.h | 34 +- src/include/optimizer/paths.h | 3 +- src/test/regress/expected/partition_join.out | 4 +- 13 files changed, 937 insertions(+), 442 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 85e4358988..0333f673b3 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -4439,15 +4439,6 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <note> <para> - Currently, pruning of partitions during the planning of an - <command>UPDATE</command> or <command>DELETE</command> command is - implemented using the constraint exclusion method (however, it is - controlled by the <literal>enable_partition_pruning</literal> rather than - <literal>constraint_exclusion</literal>) &mdash; see the following section - for details and caveats that apply. - </para> - - <para> Execution-time partition pruning currently only occurs for the <literal>Append</literal> and <literal>MergeAppend</literal> node types. It is not yet implemented for the <literal>ModifyTable</literal> node @@ -4470,9 +4461,8 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <para> <firstterm>Constraint exclusion</firstterm> is a query optimization - technique similar to partition pruning. While it is primarily used - for partitioning implemented using the legacy inheritance method, it can be - used for other purposes, including with declarative partitioning. + technique similar to partition pruning. It is primarily used + for partitioning implemented using the legacy inheritance method. </para> <para> diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 0fde876c77..f6112e1b4c 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2210,7 +2210,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); WRITE_UINT_FIELD(qual_security_level); - WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind); WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasDeletedRTEs); diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 8bd71a9825..8c72cab9c9 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -38,6 +38,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" @@ -93,8 +94,12 @@ static void set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); +static void set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte); static void set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); +static void set_inherit_target_rel_pathlists(PlannerInfo *root, + RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, @@ -121,6 +126,8 @@ static void set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); +static void inheritance_make_rel_from_joinlist(PlannerInfo *root, + List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, pushdown_safety_info *safetyInfo); static bool recurse_pushdown_safe(Node *setOp, Query *topquery, @@ -151,27 +158,6 @@ make_one_rel(PlannerInfo *root, List *joinlist) Index rti; double total_pages; - /* - * Construct the all_baserels Relids set. - */ - root->all_baserels = NULL; - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - - /* there may be empty slots corresponding to non-baserel RTEs */ - if (brel == NULL) - continue; - - Assert(brel->relid == rti); /* sanity check on array */ - - /* ignore RTEs that are "other rels" */ - if (brel->reloptkind != RELOPT_BASEREL) - continue; - - root->all_baserels = bms_add_member(root->all_baserels, brel->relid); - } - /* Mark base rels as to whether we care about fast-start plans */ set_base_rel_consider_startup(root); @@ -219,13 +205,34 @@ make_one_rel(PlannerInfo *root, List *joinlist) /* * Generate access paths for the entire join tree. + * + * For UPDATE/DELETE on an inheritance parent, join paths should be + * generated for each child result rel separately. */ - rel = make_rel_from_joinlist(root, joinlist); + if (root->parse->resultRelation && + root->simple_rte_array[root->parse->resultRelation]->inh) + { + inheritance_make_rel_from_joinlist(root, joinlist); - /* - * The result should join all and only the query's base rels. - */ - Assert(bms_equal(rel->relids, root->all_baserels)); + /* + * Return the RelOptInfo of original target relation, although this + * doesn't really contain the final path. inheritance_planner + * from where we got here will generate the final path, but it will + * do so by iterative over child subroots, not through this + * RelOptInfo. + */ + rel = find_base_rel(root, root->parse->resultRelation); + } + else + { + rel = make_rel_from_joinlist(root, joinlist); + + /* + * The result should join all and only the query's base rels. + */ + Assert(bms_equal(rel->relids, root->all_baserels)); + + } return rel; } @@ -375,8 +382,15 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_size(root, rel, rti, rte); + /* + * If it's a target relation, set the sizes of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (rti == root->parse->resultRelation) + set_inherit_target_rel_sizes(root, rel, rti, rte); + else + set_append_rel_size(root, rel, rti, rte); } else { @@ -460,14 +474,26 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { + bool inherited_update = false; + if (IS_DUMMY_REL(rel)) { /* We already proved the relation empty, so nothing more to do */ } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_pathlist(root, rel, rti, rte); + /* + * If it's a target relation, set the pathlists of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (rti == root->parse->resultRelation) + { + inherited_update = true; + set_inherit_target_rel_pathlists(root, rel, rti, rte); + } + else + set_append_rel_pathlist(root, rel, rti, rte); } else { @@ -542,8 +568,12 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, if (set_rel_pathlist_hook) (*set_rel_pathlist_hook) (root, rel, rti, rte); - /* Now find the cheapest of the paths for this rel */ - set_cheapest(rel); + /* + * Now find the cheapest of the paths for this rel, unless it's an + * inheritance parent and this is an update/delete operation. + */ + if (!inherited_update) + set_cheapest(rel); #ifdef OPTIMIZER_DEBUG debug_print_rel(root, rel); @@ -905,6 +935,321 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) } /* + * set_inherit_target_rel_sizes + * Set size estimates for the child target relations + * + * The passed-in rel represents the target relation of the query that is + * known to have inheritance children. This is very much like + * set_append_rel_size, except it doesn't set the size estimates for the + * passed-in rel itself, because we don't need to "append" the children + * in this case. + */ +static void +set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + ListCell *l; + bool has_live_children; + Relids live_children = NULL; + bool did_pruning = false; + + /* Guard against stack overflow due to overly deep inheritance tree. */ + check_stack_depth(); + + Assert(IS_SIMPLE_REL(rel)); + + /* + * If the partitioned relation has any baserestrictinfo quals then we + * attempt to use these quals to prune away partitions that cannot + * possibly contain any tuples matching these quals. In this case we'll + * store the relids of all partitions which could possibly contain a + * matching tuple, and skip anything else in the loop below. + */ + if (enable_partition_pruning && + rte->relkind == RELKIND_PARTITIONED_TABLE && + rel->baserestrictinfo != NIL) + { + live_children = prune_append_rel_partitions(rel); + did_pruning = true; + } + + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + List *childquals; + Index cq_min_security; + bool have_const_false_cq; + PlannerInfo *subroot; + ListCell *lc; + List *translated_exprs, + *child_target_exprs; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + subroot = root->inh_target_child_roots[childRTindex]; + + /* + * The child rel's RelOptInfo was already created during + * add_base_rels_to_query. + */ + childrel = find_base_rel(root, childRTindex); + Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = NIL; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, + (Node *) rinfo->clause, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + /* track minimum security level among child quals */ + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might have + * securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals, see expand_inherited_rtentry().) Pull any such + * securityQuals up into the baserestrictinfo for the child. This is + * similar to process_security_barrier_quals() for the parent rel, + * except that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* not likely that we'd see constants here, so no check */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, false, false, + security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* + * OK, we've got all the baserestrictinfo quals for this child. + */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) + { + /* This partition was pruned; skip it. */ + set_dummy_rel_pathlist(childrel); + continue; + } + + if (relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* + * This child need not be scanned, so we can omit it from the + * appendrel. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Add missing Vars to child's reltarget. + * + * add_inherit_target_child_root() would've added only those that are + * needed to be present in the top-level tlist (or ones that + * preprocess_targetlist thinks are needed to be in the tlist.) We + * may need other attributes such as those contained in WHERE clauses, + * which are already computed for the parent during + * deconstruct_jointree processing of the original query (child's + * query never goes through deconstruct_jointree.) + */ + translated_exprs = (List *) + adjust_appendrel_attrs(root, + (Node *) rel->reltarget->exprs, + 1, &appinfo); + child_target_exprs = childrel->reltarget->exprs; + foreach(lc, translated_exprs) + { + Expr *expr = lfirst(lc); + + if (!list_member(child_target_exprs, expr)) + child_target_exprs = lappend(child_target_exprs, expr); + } + + /* + * We have to make child entries in the EquivalenceClass data + * structures as well. This is needed either if the parent + * participates in some eclass joins (because we will want to consider + * inner-indexscan joins on the individual children) or if the parent + * has useful pathkeys (because we should try to build MergeAppend + * paths that produce those sort orderings). Even if this child is + * deemed dummy, it may fall on nullable side in a child-join, which + * in turn may participate in a MergeAppend, where we will need the + * EquivalenceClass data structures. + */ + if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) + add_child_rel_equivalences(subroot, appinfo, rel, childrel, true); + childrel->has_eclass_joins = rel->has_eclass_joins; + + /* CE failed, so finish copying/modifying join quals. */ + childrel->joininfo = (List *) + adjust_appendrel_attrs(subroot, + (Node *) rel->joininfo, + 1, &appinfo); + + /* + * If parallelism is allowable for this query in general, see whether + * it's allowable for this childrel in particular. But if we've + * already decided the appendrel is not parallel-safe as a whole, + * there's no point in considering parallelism for this child. For + * consistency, do this before calling set_rel_size() for the child. + */ + if (root->glob->parallelModeOK && rel->consider_parallel) + set_rel_consider_parallel(subroot, childrel, childRTE); + + /* + * Compute the child's size using possibly modified subroot. + */ + set_rel_size(subroot, childrel, childRTindex, childRTE); + + /* + * It is possible that constraint exclusion detected a contradiction + * within a child subquery, even though we didn't prove one above. If + * so, we can skip this child. + */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* + * If any live child is not parallel-safe, treat the whole appendrel + * as not parallel-safe. In future we might be able to generate plans + * in which some children are farmed out to workers while others are + * not; but we don't have that today, so it's a waste to consider + * partial paths anywhere in the appendrel unless it's all safe. + * (Child rels visited before this one will be unmarked in + * set_append_rel_pathlist().) + */ + if (!childrel->consider_parallel) + rel->consider_parallel = false; + + /* + * Accumulate size information from each live child. + */ + Assert(childrel->rows > 0); + + /* We have at least one live child. */ + has_live_children = true; + } + + if (!has_live_children) + { + /* + * All children were excluded by constraints, so mark the relation + * ass dummy. We must do this in this phase so that the rel's + * dummy-ness is visible when we generate paths for other rels. + */ + set_dummy_rel_pathlist(rel); + } + else + /* + * Set a non-zero value here to cope with the caller's requirement + * that non-dummy relations are actually not empty. We don't try to + * be accurate here, because we're not going to create a path that + * combines the children outputs. + */ + rel->rows = 1; +} + +/* * set_append_rel_size * Set size estimates for a simple "append relation" * @@ -1047,7 +1392,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, * EquivalenceClass data structures. */ if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) - add_child_rel_equivalences(root, appinfo, rel, childrel); + add_child_rel_equivalences(root, appinfo, rel, childrel, + false); childrel->has_eclass_joins = rel->has_eclass_joins; /* @@ -1329,6 +1675,63 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, } /* + * set_inherit_target_rel_pathlists + * Build access paths for the child target relations + * + * Similar to set_append_rel_pathlist, except that we build paths of the + * children, but don't build an Append path. + */ +static void +set_inherit_target_rel_pathlists(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + ListCell *l; + + /* Nothing to do if all the children were excluded. */ + if (IS_DUMMY_REL(rel)) + return; + + /* Generate access paths for each of the children of passed-in rel */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + /* Re-locate the child RTE and RelOptInfo */ + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + childrel = root->simple_rel_array[childRTindex]; + + /* + * If set_append_rel_size() decided the parent appendrel was + * parallel-unsafe at some point after visiting this child rel, we + * need to propagate the unsafety marking down to the child, so that + * we don't generate useless partial paths for it. + */ + if (!rel->consider_parallel) + childrel->consider_parallel = false; + + /* + * Compute the child's access paths. + */ + set_rel_pathlist(root, childrel, childRTindex, childRTE); + + /* + * If child is dummy, ignore it. + */ + if (IS_DUMMY_REL(childrel)) + continue; + } +} + +/* * set_append_rel_pathlist * Build access paths for an "append relation" */ @@ -2626,6 +3029,139 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows) } /* + * inheritance_make_rel_from_joinlist + * Perform join planning for all non-dummy leaf inheritance children + * in their role as an UPDATE/DELETE query's target relation + * + * If a child relation is a partitioned table, its children are processed in + * turn by recursively calling this function. + */ +static void +inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; +#ifdef USE_ASSERT_CHECKING + Relids all_baserels; +#endif + + /* + * For UPDATE/DELETE queries, the top parent can only ever be a table. + * As a contrast, it could be a UNION ALL subquery in the case of SELECT. + */ + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + Assert(planner_rt_fetch(resultRelation, root)->rtekind == RTE_RELATION); + + /* Nothing to do. */ + if (IS_DUMMY_REL(find_base_rel(root, resultRelation))) + return; + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + PlannerInfo *subroot; + RelOptInfo *childrel; + List *translated_joinlist; + + if (appinfo->parent_relid != resultRelation) + continue; + + subroot = root->inh_target_child_roots[appinfo->child_relid]; + Assert(subroot->parse->resultRelation > 0); + childrel = find_base_rel(root, appinfo->child_relid); + + /* Ignore excluded/pruned children. */ + if (IS_DUMMY_REL(childrel)) + continue; + + root->inh_target_child_rels = lappend_int(root->inh_target_child_rels, + appinfo->child_relid); + + /* + * Modify joinlist such that relations joined to the top parent rel + * appear to be joined to the child rel instead. Do the same for + * any SpecialJoinInfo structs. + */ + translated_joinlist = (List *) + adjust_appendrel_attrs(subroot, + (Node *) joinlist, + 1, &appinfo); + subroot->join_info_list = (List *) + adjust_appendrel_attrs(subroot, + (Node *) root->join_info_list, + 1, &appinfo); + + /* + * Sub-partitioned tables have to be processed recursively using the + * translated subroot as the parent, because AppendRelInfos link + * sub-partitions to their immediate parents, not the root partitioned + * table. + */ + if (childrel->part_scheme != NULL) + { + inheritance_make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * Add this child relation as a placeholder in the parent root's + * inh_target_child_joinrels so that inheritance_planner see same + * number of entries as inh_target_child_rels. + */ + root->inh_target_child_joinrels = + lappend(root->inh_target_child_joinrels, childrel); + + /* Also propagate this child's own children into parent's list. */ + if (subroot->inh_target_child_rels != NIL) + { + root->inh_target_child_rels = + list_concat(root->inh_target_child_rels, + subroot->inh_target_child_rels); + root->inh_target_child_joinrels = + list_concat(root->inh_target_child_joinrels, + subroot->inh_target_child_joinrels); + } + continue; + } + + /* + * Since we added the child rel directly into the join tree, we must + * modify it to be a "base" rel instead of an "other" rel, which the + * join planning code expects the relations being joined to be. + */ + childrel->reloptkind = RELOPT_BASEREL; + + Assert(subroot->join_rel_list == NIL); + Assert(subroot->join_rel_hash == NULL); + + /* Perform join planning and save the resulting RelOptInfo. */ + childrel = make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * Remember this child target rel. inheritance_planner will perform + * the remaining steps of planning for each child relation separately. + * Specifically, it will call grouping_planner on every + * RelOptInfo contained in the inh_target_child_rels list, each of + * which represents the source of tuples to be modified for a given + * target child rel. + */ + root->inh_target_child_joinrels = + lappend(root->inh_target_child_joinrels, childrel); +#ifdef USE_ASSERT_CHECKING + /* + * The following implements essentially the same Assert as in + * make_one_rel, our caller. + */ + all_baserels = bms_copy(root->all_baserels); + all_baserels = bms_del_member(all_baserels, + root->parse->resultRelation); + all_baserels = bms_add_member(all_baserels, + subroot->parse->resultRelation); + Assert(bms_equal(childrel->relids, all_baserels)); +#endif + } +} + +/* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. * diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 7fad7ad1bf..8b4ba1a828 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -2105,12 +2105,18 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root, * * parent_rel and child_rel could be derived from appinfo, but since the * caller has already computed them, we might as well just pass them in. + * + * If 'child_is_target' is true then the child EC members *replace* the + * corresponding parent members. In that case, 'root' is the child target + * relation's dedicated PlannerInfo so it makes sense to remove the parent + * ECs altogether, because they're of no use. */ void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, - RelOptInfo *child_rel) + RelOptInfo *child_rel, + bool child_is_target) { ListCell *lc1; @@ -2118,6 +2124,8 @@ add_child_rel_equivalences(PlannerInfo *root, { EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc1); ListCell *lc2; + ListCell *prev; + ListCell *next; /* * If this EC contains a volatile expression, then generating child @@ -2135,12 +2143,18 @@ add_child_rel_equivalences(PlannerInfo *root, !bms_is_subset(parent_rel->relids, cur_ec->ec_relids)) continue; - foreach(lc2, cur_ec->ec_members) + prev = NULL; + for (lc2 = list_head(cur_ec->ec_members); lc2 != NULL; lc2 = next) { EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2); + next = lnext(lc2); + if (cur_em->em_is_const) + { + prev = lc2; continue; /* ignore consts here */ + } /* Does it reference parent_rel? */ if (bms_overlap(cur_em->em_relids, parent_rel->relids)) @@ -2178,10 +2192,17 @@ add_child_rel_equivalences(PlannerInfo *root, child_rel->relids); } + /* Delete the parent EC member. */ + if (child_is_target) + cur_ec->ec_members = list_delete_cell(cur_ec->ec_members, + lc2, prev); + (void) add_eq_member(cur_ec, child_expr, new_relids, new_nullable_relids, - true, cur_em->em_datatype); + !child_is_target, cur_em->em_datatype); } + else + prev = lc2; } } } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 066685c3c7..8f1769b5c6 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -2022,12 +2022,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) /* * During setrefs.c, we'll need the grouping_map to fix up the cols lists * in GroupingFunc nodes. Save it for setrefs.c to use. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be grouping in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->grouping_map == NULL); root->grouping_map = grouping_map; @@ -2184,12 +2179,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) * with InitPlan output params. (We can't just do that locally in the * MinMaxAgg node, because path nodes above here may have Agg references * as well.) Save the mmaggregates list to tell setrefs.c to do that. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be aggregates in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->minmax_aggs == NIL); root->minmax_aggs = best_path->mmaggregates; diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index fc97a1bb50..4f68ad9907 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -28,7 +28,11 @@ #include "optimizer/paths.h" #include "optimizer/placeholder.h" #include "optimizer/planmain.h" +#include "optimizer/prep.h" +static void add_inherited_target_child_roots(PlannerInfo *root); +static PlannerInfo *adjust_inherited_target_child_root(PlannerInfo *root, + AppendRelInfo *appinfo); /* * query_planner @@ -59,6 +63,8 @@ query_planner(PlannerInfo *root, List *tlist, Query *parse = root->parse; List *joinlist; RelOptInfo *final_rel; + bool inherited_update; + Index rti; /* * If the query has an empty join tree, then it's something easy like @@ -232,14 +238,168 @@ query_planner(PlannerInfo *root, List *tlist, extract_restriction_or_clauses(root); /* + * Construct the all_baserels Relids set. + */ + root->all_baserels = NULL; + for (rti = 1; rti < root->simple_rel_array_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + Assert(brel->relid == rti); /* sanity check on array */ + + /* ignore RTEs that are "other rels" */ + if (brel->reloptkind != RELOPT_BASEREL) + continue; + + root->all_baserels = bms_add_member(root->all_baserels, brel->relid); + } + + inherited_update = (parse->resultRelation && + root->simple_rte_array[parse->resultRelation]->inh); + + /* + * Add child subroots needed to use during planning for individual child + * targets + */ + if (inherited_update) + { + root->inh_target_child_roots = (PlannerInfo **) + palloc0(root->simple_rel_array_size * + sizeof(PlannerInfo *)); + add_inherited_target_child_roots(root); + } + + /* * Ready to do the primary planning. */ final_rel = make_one_rel(root, joinlist); - /* Check that we got at least one usable path */ - if (!final_rel || !final_rel->cheapest_total_path || - final_rel->cheapest_total_path->param_info != NULL) + /* + * Check that we got at least one usable path. In the case of an + * inherited update/delete operation, no path has been created for + * the query's actual target relation yet. + */ + if (!inherited_update && + (!final_rel || + !final_rel->cheapest_total_path || + final_rel->cheapest_total_path->param_info != NULL)) elog(ERROR, "failed to construct the join relation"); return final_rel; } + +/* + * add_inherited_target_child_roots + * Add PlannerInfos for inheritance target children + */ +static void +add_inherited_target_child_roots(PlannerInfo *root) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; + + Assert(root->inh_target_child_roots != NULL); + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + RangeTblEntry *childRTE; + PlannerInfo *subroot; + + if (appinfo->parent_relid != resultRelation) + continue; + + childRTE = root->simple_rte_array[appinfo->child_relid]; + subroot = adjust_inherited_target_child_root(root, appinfo); + + if (childRTE->inh) + add_inherited_target_child_roots(subroot); + + root->inh_target_child_roots[appinfo->child_relid] = subroot; + } +} + +/* + * add_inherit_target_child_root + * This translates query to match the child given by appinfo and + * puts it in a PlannerInfo that will be used for planning the child + * + * The child PlannerInfo reuses most of the parent PlannerInfo's fields + * unchanged, except unexpanded_tlist and processed_tlist are based on the + * child relation. + */ +static PlannerInfo * +adjust_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) +{ + PlannerInfo *subroot; + List *tlist; + ListCell *lc; + + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + + /* Translate the original query's expressions to this child. */ + subroot = makeNode(PlannerInfo); + memcpy(subroot, root, sizeof(PlannerInfo)); + + /* + * Restore the unexpanded tlist for translation, so that child's + * query contains targetList numbered (resnos) per its own + * TupleDesc, which adjust_inherited_tlist ensures. + */ + root->parse->targetList = root->unexpanded_tlist; + subroot->parse = (Query *) adjust_appendrel_attrs(root, + (Node *) root->parse, + 1, &appinfo); + + /* + * Save the original unexpanded targetlist in child subroot, just as + * we did for the parent root, so that this child's own children can use + * it. Must use copy because subroot->parse->targetList will be modified + * soon. + */ + subroot->unexpanded_tlist = list_copy(subroot->parse->targetList); + + /* + * Apply planner's expansion of targetlist, such as adding various junk + * column, filling placeholder entries for dropped columns, etc., all of + * which occurs with the child's TupleDesc. + */ + tlist = preprocess_targetlist(subroot); + subroot->processed_tlist = tlist; + + /* Add any newly added Vars to the child RelOptInfo. */ + build_base_rel_tlists(subroot, tlist); + + /* + * Adjust all_baserels to replace the original target relation with the + * child target relation. Copy it before modifying though. + */ + subroot->all_baserels = bms_copy(root->all_baserels); + subroot->all_baserels = bms_del_member(subroot->all_baserels, + root->parse->resultRelation); + subroot->all_baserels = bms_add_member(subroot->all_baserels, + subroot->parse->resultRelation); + + /* + * Child root should get its own copy of ECs, because they'll be modified + * to replace parent EC expressions by child expressions in + * add_child_rel_equivalences. + */ + subroot->eq_classes = NIL; + foreach(lc, root->eq_classes) + { + EquivalenceClass *ec = lfirst(lc); + EquivalenceClass *new_ec = makeNode(EquivalenceClass); + + memcpy(new_ec, ec, sizeof(EquivalenceClass)); + new_ec->ec_members = list_copy(ec->ec_members); + subroot->eq_classes = lappend(subroot->eq_classes, new_ec); + } + + return subroot; +} diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 98acb58304..bea89dd8dd 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -37,6 +37,7 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "nodes/relation.h" #include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" @@ -128,7 +129,7 @@ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static void inheritance_planner(PlannerInfo *root); static void grouping_planner(PlannerInfo *root, bool inheritance_update, - double tuple_fraction); + RelOptInfo *planned_rel, double tuple_fraction); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, int *tleref_to_colnum_map); @@ -632,7 +633,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->grouping_map = NULL; root->minmax_aggs = NIL; root->qual_security_level = 0; - root->inhTargetKind = INHKIND_NONE; root->hasRecursion = hasRecursion; if (hasRecursion) root->wt_param_id = SS_assign_special_param(root); @@ -972,7 +972,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh) inheritance_planner(root); else - grouping_planner(root, false, tuple_fraction); + grouping_planner(root, false, NULL, tuple_fraction); /* * Capture the set of outer-level param IDs we have access to, for use in @@ -1148,12 +1148,19 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr) * inheritance set. * * We have to handle this case differently from cases where a source relation - * is an inheritance set. Source inheritance is expanded at the bottom of the - * plan tree (see allpaths.c), but target inheritance has to be expanded at - * the top. The reason is that for UPDATE, each target relation needs a - * different targetlist matching its own column set. Fortunately, - * the UPDATE/DELETE target can never be the nullable side of an outer join, - * so it's OK to generate the plan this way. + * is an inheritance set. That's mainly because for UPDATE, each target + * relation needs a different targetlist matching its own column set. So, we + * must modify the source scan/join path for each target relation such that it + * produces the desired target list. + * + * The source scan/join paths for individual target relations are still + * created in allpaths.c by first expanding the inheritance in the usual way + * by set_append_rel_size, followed by join planning for each target relation + * separately in make_one_rel. Finally, we apply grouping_planner here to each + * child scan/join path so that it produces the desired targetlist. + * + * Fortunately, the UPDATE/DELETE target can never be the nullable side of an + * outer join, so it's OK to generate the plan this way. * * Returns nothing; the useful output is in the Paths we attach to * the (UPPERREL_FINAL, NULL) upperrel stored in *root. @@ -1166,14 +1173,8 @@ inheritance_planner(PlannerInfo *root) { Query *parse = root->parse; int top_parentRTindex = parse->resultRelation; - Bitmapset *subqueryRTindexes; - Bitmapset *modifiableARIindexes; int nominalRelation = -1; Index rootRelation = 0; - List *final_rtable = NIL; - int save_rel_array_size = 0; - RelOptInfo **save_rel_array = NULL; - AppendRelInfo **save_append_rel_array = NULL; List *subpaths = NIL; List *subroots = NIL; List *resultRelations = NIL; @@ -1181,70 +1182,59 @@ inheritance_planner(PlannerInfo *root) List *returningLists = NIL; List *rowMarks; RelOptInfo *final_rel; - ListCell *lc; - Index rti; + ListCell *lc1, + *lc2; RangeTblEntry *parent_rte; - PlannerInfo *parent_root; - Query *parent_parse; - Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); - PlannerInfo **parent_roots = NULL; + List *tlist; + standard_qp_extra qp_extra; + RelOptInfo *planned_rel; + /* Inheritance is never used for insert. */ Assert(parse->commandType != CMD_INSERT); + parent_rte = planner_rt_fetch(top_parentRTindex, root); /* - * We generate a modified instance of the original Query for each target - * relation, plan that, and put all the plans into a list that will be - * controlled by a single ModifyTable node. All the instances share the - * same rangetable, but each instance must have its own set of subquery - * RTEs within the finished rangetable because (1) they are likely to get - * scribbled on during planning, and (2) it's not inconceivable that - * subqueries could get planned differently in different cases. We need - * not create duplicate copies of other RTE kinds, in particular not the - * target relations, because they don't have either of those issues. Not - * having to duplicate the target relations is important because doing so - * (1) would result in a rangetable of length O(N^2) for N targets, with - * at least O(N^3) work expended here; and (2) would greatly complicate - * management of the rowMarks list. + * Generate the access paths for all relations mentioned in the query, + * including the target inheritance set. When doing the join planning, + * references in the join tree to the original target relation that's the + * root parent of the inheritance tree is replaced by each of its + * inheritance children and the resulting joinrel RelOptInfo's are + * added to root->inh_target_child_rels. * - * To begin with, generate a bitmapset of the relids of the subquery RTEs. + * Final planning steps (grouping_planner) are applied to the best path + * of each of those child joinrels using a modified instance of the + * original query for a given child target rel. All the paths so + * generated are put into a list that will be controlled by a single + * ModifyTable node. All the instances share the same rangetable. */ - subqueryRTindexes = NULL; - rti = 1; - foreach(lc, parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - if (rte->rtekind == RTE_SUBQUERY) - subqueryRTindexes = bms_add_member(subqueryRTindexes, rti); - rti++; - } /* - * Next, we want to identify which AppendRelInfo items contain references - * to any of the aforesaid subquery RTEs. These items will need to be - * copied and modified to adjust their subquery references; whereas the - * other ones need not be touched. It's worth being tense over this - * because we can usually avoid processing most of the AppendRelInfo - * items, thereby saving O(N^2) space and time when the target is a large - * inheritance tree. We can identify AppendRelInfo items by their - * child_relid, since that should be unique within the list. + * Save the unexpanded version of the query's targetlist to be used below + * for passing to grouping_planner for each child target relation. */ - modifiableARIindexes = NULL; - if (subqueryRTindexes != NULL) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); + root->unexpanded_tlist = list_copy(root->parse->targetList); - if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) || - bms_is_member(appinfo->child_relid, subqueryRTindexes) || - bms_overlap(pull_varnos((Node *) appinfo->translated_vars), - subqueryRTindexes)) - modifiableARIindexes = bms_add_member(modifiableARIindexes, - appinfo->child_relid); - } + /* Do the scan/join planning. */ + tlist = preprocess_targetlist(root); + root->processed_tlist = tlist; + qp_extra.tlist = tlist; + qp_extra.activeWindows = qp_extra.groupClause = NIL; + planned_rel = query_planner(root, tlist, standard_qp_callback, &qp_extra); + + /* + * If it turned out during query planning that all the children are dummy + * (pruned or excluded by constraints), no need to do the steps below. + * Let grouping_planner finish up the final path. + */ + if (IS_DUMMY_REL(planned_rel)) + { + grouping_planner(root, false, planned_rel, 0.0); + return; } + Assert(planned_rel->relid == top_parentRTindex); + Assert(planned_rel->reloptkind == RELOPT_BASEREL); + /* * If the parent RTE is a partitioned table, we should use that as the * nominal target relation, because the RTEs added for partitioned tables @@ -1252,7 +1242,6 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1260,95 +1249,42 @@ inheritance_planner(PlannerInfo *root) } /* - * The PlannerInfo for each child is obtained by translating the relevant - * members of the PlannerInfo for its immediate parent, which we find - * using the parent_relid in its AppendRelInfo. We save the PlannerInfo - * for each parent in an array indexed by relid for fast retrieval. Since - * the maximum number of parents is limited by the number of RTEs in the - * query, we use that number to allocate the array. An extra entry is - * needed since relids start from 1. + * Get on with finalizing the path for each child target relation by + * calling grouping_planner on its joinrel. Note that we're restoring + * the query's targetlist to the original one for grouping_planner's + * targetlist expansion steps to perform the expansion with individual + * child descriptors. */ - parent_roots = (PlannerInfo **) palloc0((list_length(parse->rtable) + 1) * - sizeof(PlannerInfo *)); - parent_roots[top_parentRTindex] = root; - - /* - * And now we can get on with generating a plan for each child table. - */ - foreach(lc, root->append_rel_list) + forboth(lc1, root->inh_target_child_rels, + lc2, root->inh_target_child_joinrels) { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - PlannerInfo *subroot; + Index childRTindex = lfirst_int(lc1); + RelOptInfo *childrel = lfirst(lc2); + PlannerInfo *subroot = root->inh_target_child_roots[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; RangeTblEntry *child_rte; RelOptInfo *sub_final_rel; Path *subpath; - /* append_rel_list contains all append rels; ignore others */ - if (!bms_is_member(appinfo->parent_relid, parent_relids)) - continue; - - /* - * expand_inherited_rtentry() always processes a parent before any of - * that parent's children, so the parent_root for this relation should - * already be available. - */ - parent_root = parent_roots[appinfo->parent_relid]; - Assert(parent_root != NULL); - parent_parse = parent_root->parse; - - /* - * We need a working copy of the PlannerInfo so that we can control - * propagation of information back to the main copy. - */ - subroot = makeNode(PlannerInfo); - memcpy(subroot, parent_root, sizeof(PlannerInfo)); - - /* - * Generate modified query with this rel as target. We first apply - * adjust_appendrel_attrs, which copies the Query and changes - * references to the parent RTE to refer to the current child RTE, - * then fool around with subquery RTEs. - */ - subroot->parse = (Query *) - adjust_appendrel_attrs(parent_root, - (Node *) parent_parse, - 1, &appinfo); + Assert(subroot != NULL); + Assert(subroot->parse->resultRelation == childRTindex); /* * If there are securityQuals attached to the parent, move them to the * child rel (they've already been transformed properly for that). */ - parent_rte = rt_fetch(appinfo->parent_relid, subroot->parse->rtable); - child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable); + parent_rte = planner_rt_fetch(appinfo->parent_relid, subroot); + child_rte = planner_rt_fetch(appinfo->child_relid, subroot); child_rte->securityQuals = parent_rte->securityQuals; parent_rte->securityQuals = NIL; /* - * Mark whether we're planning a query to a partitioned table or an - * inheritance parent. + * Ignore a partitioned child. Instead, the paths of its children will + * be added to subpaths. */ - subroot->inhTargetKind = - (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED; - - /* - * If this child is further partitioned, remember it as a parent. - * Since a partitioned table does not have any data, we don't need to - * create a plan for it, and we can stop processing it here. We do, - * however, need to remember its modified PlannerInfo for use when - * processing its children, since we'll update their varnos based on - * the delta from immediate parent to child, not from top to child. - * - * Note: a very non-obvious point is that we have not yet added - * duplicate subquery RTEs to the subroot's rtable. We mustn't, - * because then its children would have two sets of duplicates, - * confusing matters. - */ - if (child_rte->inh) + if (childrel->part_scheme) { - Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE); - parent_relids = bms_add_member(parent_relids, appinfo->child_relid); - parent_roots[appinfo->child_relid] = subroot; - + Assert(child_rte->inh); continue; } @@ -1376,111 +1312,8 @@ inheritance_planner(PlannerInfo *root) if (nominalRelation < 0) nominalRelation = appinfo->child_relid; - /* - * The rowMarks list might contain references to subquery RTEs, so - * make a copy that we can apply ChangeVarNodes to. (Fortunately, the - * executor doesn't need to see the modified copies --- we can just - * pass it the original rowMarks list.) - */ - subroot->rowMarks = copyObject(parent_root->rowMarks); - - /* - * The append_rel_list likewise might contain references to subquery - * RTEs (if any subqueries were flattenable UNION ALLs). So prepare - * to apply ChangeVarNodes to that, too. As explained above, we only - * want to copy items that actually contain such references; the rest - * can just get linked into the subroot's append_rel_list. - * - * If we know there are no such references, we can just use the outer - * append_rel_list unmodified. - */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - subroot->append_rel_list = NIL; - foreach(lc2, parent_root->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, modifiableARIindexes)) - appinfo2 = copyObject(appinfo2); - - subroot->append_rel_list = lappend(subroot->append_rel_list, - appinfo2); - } - } - - /* - * Add placeholders to the child Query's rangetable list to fill the - * RT indexes already reserved for subqueries in previous children. - * These won't be referenced, so there's no need to make them very - * valid-looking. - */ - while (list_length(subroot->parse->rtable) < list_length(final_rtable)) - subroot->parse->rtable = lappend(subroot->parse->rtable, - makeNode(RangeTblEntry)); - - /* - * If this isn't the first child Query, generate duplicates of all - * subquery RTEs, and adjust Var numbering to reference the - * duplicates. To simplify the loop logic, we scan the original rtable - * not the copy just made by adjust_appendrel_attrs; that should be OK - * since subquery RTEs couldn't contain any references to the target - * rel. - */ - if (final_rtable != NIL && subqueryRTindexes != NULL) - { - ListCell *lr; - - rti = 1; - foreach(lr, parent_parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lr); - - if (bms_is_member(rti, subqueryRTindexes)) - { - Index newrti; - - /* - * The RTE can't contain any references to its own RT - * index, except in its securityQuals, so we can save a - * few cycles by applying ChangeVarNodes to the rest of - * the rangetable before we append the RTE to it. - */ - newrti = list_length(subroot->parse->rtable) + 1; - ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); - ChangeVarNodes((Node *) subroot->rowMarks, rti, newrti, 0); - /* Skip processing unchanging parts of append_rel_list */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - foreach(lc2, subroot->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, - modifiableARIindexes)) - ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); - } - } - rte = copyObject(rte); - ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); - subroot->parse->rtable = lappend(subroot->parse->rtable, - rte); - } - rti++; - } - } - - /* There shouldn't be any OJ info to translate, as yet */ - Assert(subroot->join_info_list == NIL); - /* and we haven't created PlaceHolderInfos, either */ - Assert(subroot->placeholder_list == NIL); - - /* Generate Path(s) for accessing this result relation */ - grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ ); + /* Apply the top-level targetlist to childrel's path. */ + grouping_planner(subroot, true, childrel, 0.0); /* * Select cheapest path in case there's more than one. We always run @@ -1492,45 +1325,10 @@ inheritance_planner(PlannerInfo *root) subpath = sub_final_rel->cheapest_total_path; /* - * If this child rel was excluded by constraint exclusion, exclude it - * from the result plan. + * child rel cannot be empty, or inheritance_make_rel_from_joinlist + * wouldn't have put it in the list to begin with. */ - if (IS_DUMMY_PATH(subpath)) - continue; - - /* - * If this is the first non-excluded child, its post-planning rtable - * becomes the initial contents of final_rtable; otherwise, append - * just its modified subquery RTEs to final_rtable. - */ - if (final_rtable == NIL) - final_rtable = subroot->parse->rtable; - else - final_rtable = list_concat(final_rtable, - list_copy_tail(subroot->parse->rtable, - list_length(final_rtable))); - - /* - * We need to collect all the RelOptInfos from all child plans into - * the main PlannerInfo, since setrefs.c will need them. We use the - * last child's simple_rel_array (previous ones are too short), so we - * have to propagate forward the RelOptInfos that were already built - * in previous children. - */ - Assert(subroot->simple_rel_array_size >= save_rel_array_size); - for (rti = 1; rti < save_rel_array_size; rti++) - { - RelOptInfo *brel = save_rel_array[rti]; - - if (brel) - subroot->simple_rel_array[rti] = brel; - } - save_rel_array_size = subroot->simple_rel_array_size; - save_rel_array = subroot->simple_rel_array; - save_append_rel_array = subroot->append_rel_array; - - /* Make sure any initplans from this rel get into the outer list */ - root->init_plans = subroot->init_plans; + Assert(!IS_DUMMY_PATH(subpath)); /* Build list of sub-paths */ subpaths = lappend(subpaths, subpath); @@ -1562,36 +1360,6 @@ inheritance_planner(PlannerInfo *root) */ /* - * If we managed to exclude every child rel, return a dummy plan; it - * doesn't even need a ModifyTable node. - */ - if (subpaths == NIL) - { - set_dummy_rel_pathlist(final_rel); - return; - } - - /* - * Put back the final adjusted rtable into the master copy of the Query. - * (We mustn't do this if we found no non-excluded children.) - */ - parse->rtable = final_rtable; - root->simple_rel_array_size = save_rel_array_size; - root->simple_rel_array = save_rel_array; - root->append_rel_array = save_append_rel_array; - - /* Must reconstruct master's simple_rte_array, too */ - root->simple_rte_array = (RangeTblEntry **) - palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *)); - rti = 1; - foreach(lc, final_rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - root->simple_rte_array[rti++] = rte; - } - - /* * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will * have dealt with fetching non-locked marked rows, else we need to have * ModifyTable do that. @@ -1631,6 +1399,12 @@ inheritance_planner(PlannerInfo *root) * (inheritance_planner will create a single ModifyTable node covering all the * target tables.) * + * If non-NULL, planned_rel is a RelOptInfo containing paths for the query's + * top-level joinrel, which the caller produced by itself. In that case, this + * function only needs to adjust the targetlist of its cheapest_total_path. + * The only caller that may pass such a RelOptInfo currently is + * inheritance_planner. + * * tuple_fraction is the fraction of tuples we expect will be retrieved. * tuple_fraction is interpreted as follows: * 0: expect all tuples to be retrieved (normal case) @@ -1649,6 +1423,7 @@ inheritance_planner(PlannerInfo *root) */ static void grouping_planner(PlannerInfo *root, bool inheritance_update, + RelOptInfo *planned_rel, double tuple_fraction) { Query *parse = root->parse; @@ -1661,7 +1436,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, List *final_targets; List *final_targets_contain_srfs; bool final_target_parallel_safe; - RelOptInfo *current_rel; + RelOptInfo *current_rel = planned_rel; RelOptInfo *final_rel; ListCell *lc; @@ -1701,6 +1476,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * special work for recursive unions is the responsibility of * plan_set_operations. */ + Assert(current_rel == NULL); current_rel = plan_set_operations(root); /* @@ -1790,17 +1566,26 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, parse->groupClause = preprocess_groupclause(root, NIL); } - /* Preprocess targetlist */ - tlist = preprocess_targetlist(root); - /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation so - * that we can transfer its decoration (resnames etc) to the topmost - * tlist of the finished Plan. + * Preprocess targetlist, if needed. If the caller has already done + * query planning, root->processed_tlist already contains the desired + * targetlist. */ - root->processed_tlist = tlist; + if (planned_rel == NULL) + { + tlist = preprocess_targetlist(root); + + /* + * We are now done hacking up the query's targetlist. Most of the + * remaining planning work will be done with the PathTarget + * representation of tlists, but save aside the full representation + * so that we can transfer its decoration (resnames etc) to the + * topmost tlist of the finished Plan. + */ + root->processed_tlist = tlist; + } + else + tlist = root->processed_tlist; /* * Collect statistics about aggregates for estimating costs, and mark @@ -1880,8 +1665,9 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * We also generate (in standard_qp_callback) pathkey representations * of the query's sort clause, distinct clause, etc. */ - current_rel = query_planner(root, tlist, - standard_qp_callback, &qp_extra); + if (current_rel == NULL) + current_rel = query_planner(root, tlist, + standard_qp_callback, &qp_extra); /* * Convert the query's result tlist into PathTarget format. diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 77dbf4eba3..87adfa02b1 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -917,7 +917,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->grouping_map = NULL; subroot->minmax_aggs = NIL; subroot->qual_security_level = 0; - subroot->inhTargetKind = INHKIND_NONE; subroot->hasRecursion = false; subroot->wt_param_id = -1; subroot->non_recursive_path = NULL; diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index 01040c5cfe..be6ce11da4 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -447,8 +447,39 @@ adjust_appendrel_attrs_mutator(Node *node, context->appinfos); return (Node *) phv; } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 48ffc5f254..51d8faf2dd 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1264,36 +1264,6 @@ get_relation_constraints(PlannerInfo *root, } } - /* - * Append partition predicates, if any. - * - * For selects, partition pruning uses the parent table's partition bound - * descriptor, instead of constraint exclusion which is driven by the - * individual partition's partition constraint. - */ - if (enable_partition_pruning && root->parse->commandType != CMD_SELECT) - { - List *pcqual = RelationGetPartitionQual(relation); - - if (pcqual) - { - /* - * Run the partition quals through const-simplification similar to - * check constraints. We skip canonicalize_qual, though, because - * partition quals should be in canonical form already; also, - * since the qual is in implicit-AND format, we'd have to - * explicitly convert it to explicit-AND format and back again. - */ - pcqual = (List *) eval_const_expressions(root, (Node *) pcqual); - - /* Fix Vars to have the desired varno */ - if (varno != 1) - ChangeVarNodes((Node *) pcqual, 1, varno, 0); - - result = list_concat(result, pcqual); - } - } - heap_close(relation, NoLock); return result; @@ -1420,31 +1390,15 @@ relation_excluded_by_constraints(PlannerInfo *root, switch (constraint_exclusion) { case CONSTRAINT_EXCLUSION_OFF: - - /* - * Don't prune if feature turned off -- except if the relation is - * a partition. While partprune.c-style partition pruning is not - * yet in use for all cases (update/delete is not handled), it - * would be a UI horror to use different user-visible controls - * depending on such a volatile implementation detail. Therefore, - * for partitioned tables we use enable_partition_pruning to - * control this behavior. - */ - if (root->inhTargetKind == INHKIND_PARTITIONED) - break; return false; case CONSTRAINT_EXCLUSION_PARTITION: /* * When constraint_exclusion is set to 'partition' we only handle - * OTHER_MEMBER_RELs, or BASERELs in cases where the result target - * is an inheritance parent or a partitioned table. + * OTHER_MEMBER_RELs. */ - if ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) && - !(rel->reloptkind == RELOPT_BASEREL && - root->inhTargetKind != INHKIND_NONE && - rel->relid == root->parse->resultRelation)) + if (rel->reloptkind != RELOPT_OTHER_MEMBER_REL) return false; break; diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 3430061361..2fde95de7b 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -319,9 +319,6 @@ typedef struct PlannerInfo Index qual_security_level; /* minimum security_level for quals */ /* Note: qual_security_level is zero if there are no securityQuals */ - InheritanceKind inhTargetKind; /* indicates if the target relation is an - * inheritance child or partition or a - * partitioned table */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */ bool hasDeletedRTEs; /* true if any RTE was deleted from jointree */ @@ -343,6 +340,37 @@ typedef struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* + * The following fields are set during query planning portion of an + * inherited UPDATE/DEELETE operation. + */ + + /* + * This stores the original version of the query's targetlist that's + * not modified by the planner. + */ + List *unexpanded_tlist; + + /* + * PlannerInfos corresponding to each inheritance child targets. + * Content of each PlannerInfo is same as the parent PlannerInfo, except + * for the parse tree which is a translated copy of the parent's parse + * tree. + */ + struct PlannerInfo **inh_target_child_roots; + + /* List of RT indexes of child RT indexes. */ + List *inh_target_child_rels; + + /* + * RelOptInfos corresponding to each child target rel. For leaf children, + * it's the RelOptInfo representing the output of make_rel_from_joinlist() + * called with the parent rel in the original join tree replaced by a + * given leaf child. For non-leaf children, it's the baserel RelOptInfo + * itself, left as a placeholder. + */ + List *inh_target_child_joinrels; } PlannerInfo; diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 666217c189..a39a0a3a74 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -161,7 +161,8 @@ extern EquivalenceClass *match_eclasses_to_foreign_key_col(PlannerInfo *root, extern void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, - RelOptInfo *child_rel); + RelOptInfo *child_rel, + bool child_is_target); extern List *generate_implied_equalities_for_column(PlannerInfo *root, RelOptInfo *rel, ec_matches_callback_type callback, diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index c55de5d476..c9f02c1fb0 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1780,7 +1780,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_1 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_1 -> Nested Loop Semi Join @@ -1788,7 +1788,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_2 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_2 (28 rows) -- 2.11.0
v12-0003-Store-inheritance-root-parent-index-in-otherrel-.patch (text/plain, 2.5K) download From b38220a1ebb112701dd1fe3ba378486e89d55a6f Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 26 Oct 2018 16:45:59 +0900 Subject: [PATCH v12 3/6] Store inheritance root parent index in otherrel's RelOptInfo Although it's set by build_simple_rel, it's not being used by any code yet. --- src/backend/nodes/outfuncs.c | 1 + src/backend/optimizer/util/relnode.c | 14 ++++++++++++++ src/include/nodes/relation.h | 4 ++++ 3 files changed, 19 insertions(+) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index f6112e1b4c..5248b5a764 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2271,6 +2271,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_BOOL_FIELD(consider_partitionwise_join); WRITE_BITMAPSET_FIELD(top_parent_relids); WRITE_NODE_FIELD(partitioned_child_rels); + WRITE_UINT_FIELD(inh_root_parent); } static void diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index fe83ec4519..678da0ce55 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -216,9 +216,23 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->top_parent_relids = parent->top_parent_relids; else rel->top_parent_relids = bms_copy(parent->relids); + + /* + * For inheritance child relations, we also set inh_root_parent. + * Note that 'parent' might itself be a child (a sub-partitioned + * partition), in which case we simply use its value of + * inh_root_parent. + */ + if (parent->rtekind == RTE_RELATION) + rel->inh_root_parent = parent->inh_root_parent > 0 ? + parent->inh_root_parent : + parent->relid; } else + { rel->top_parent_relids = NULL; + rel->inh_root_parent = 0; + } /* Check type of rtable entry */ switch (rte->rtekind) diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 2fde95de7b..a2adbfc3b8 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -731,6 +731,10 @@ typedef struct RelOptInfo List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ + + Index inh_root_parent; /* For otherrels, this is the RT index of + * inheritance table mentioned in the query + * from which this relation originated */ } RelOptInfo; /* -- 2.11.0
v12-0004-Lazy-creation-of-RTEs-for-inheritance-children.patch (text/plain, 91.3K) download From fd629f03b3ba94ace91ceb3ccfd7866f74f484d3 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 18:14:13 +0900 Subject: [PATCH v12 4/6] Lazy creation of RTEs for inheritance children Currently, expand_inherited_tables called from subquery_planner opens all inheritance child tables and adds them to the query's range table and PlannerInfo in the form of AppendRelInfo's initially and later RelOptInfo's. For partitioning, that's pretty wasteful, because it's possible to determine only the partitions that will need to be scanned using partition pruning, which doesn't require opening the partitions themselves. However, the point at which the above processing happens is too early to perform partition pruning. This commit rearranges things within the planner so that inheritance child relations can be added to the Query and PlannerInfo during make_one_rel, just before set_base_rel_sizes is called. Although the late initialization approach only benefits the partitioning case, it seems undesirable to do it only for partitioned tables, because it means adding code to handle partitions specially at various places within the planner. So, *all* inheritance parent tables are expanded in make_one_rel. All unpruned partitions are added to Query in the form of their RangeTblEntry's being added to the range table and to PlannerInfo in the form of an AppendRelInfo and a RelOptInfo for each. For partitioning, although we don't create a RangeTblEntry and RelOptInfo for pruned partitions at make_one_rel time, partitionwise join code relies on the fact that even though partitions may have been pruned, they'd still own a RelOptInfo to handle the outer join case where the pruned partition appears on the nullable side of join. Partitionwise join code deals with that by allocating dummy RelOptInfos for pruned partitions that are based mostly on their parent's properties. There are some regression test diffs: 1. Caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child, as seen in the partition_aggregate.out test output. 2. Those in postgres_fdw.out are caused by the fact that junk columns required for row marking are added to reltarget->exprs later than user columns, because the row marking junk columns arent added until after the inheritance is expanded which as of this commit is later than it used to be. --- contrib/postgres_fdw/expected/postgres_fdw.out | 32 +- src/backend/optimizer/path/allpaths.c | 355 +--------- src/backend/optimizer/path/joinrels.c | 61 ++ src/backend/optimizer/plan/initsplan.c | 58 -- src/backend/optimizer/plan/planmain.c | 12 +- src/backend/optimizer/plan/planner.c | 65 +- src/backend/optimizer/plan/setrefs.c | 6 + src/backend/optimizer/prep/preptlist.c | 15 +- src/backend/optimizer/prep/prepunion.c | 4 + src/backend/optimizer/util/appendinfo.c | 45 +- src/backend/optimizer/util/inherit.c | 797 +++++++++++++++------- src/backend/optimizer/util/plancat.c | 40 +- src/backend/optimizer/util/relnode.c | 80 +-- src/backend/partitioning/partprune.c | 43 +- src/include/nodes/relation.h | 8 + src/include/optimizer/appendinfo.h | 7 +- src/include/optimizer/pathnode.h | 1 + src/include/optimizer/plancat.h | 2 +- src/include/optimizer/prep.h | 3 +- src/include/partitioning/partprune.h | 2 +- src/test/regress/expected/partition_aggregate.out | 4 +- 21 files changed, 874 insertions(+), 766 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index bb92d9d37a..33a33606ef 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7072,15 +7072,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7110,15 +7110,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7147,15 +7147,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid @@ -7165,15 +7165,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (39 rows) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 8c72cab9c9..a3a686a153 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -46,7 +46,6 @@ #include "optimizer/var.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -380,7 +379,11 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, */ set_dummy_rel_pathlist(rel); } - else if (rte->inh) + /* + * expand_inherited_tables may have proved that the relation is empty, so + * check if it's so. + */ + else if (rte->inh && !IS_DUMMY_REL(rel)) { /* * If it's a target relation, set the sizes of children instead. @@ -951,38 +954,18 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, int parentRTindex = rti; ListCell *l; bool has_live_children; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); Assert(IS_SIMPLE_REL(rel)); - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - foreach(l, root->append_rel_list) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; PlannerInfo *subroot; ListCell *lc; List *translated_exprs, @@ -997,139 +980,18 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, subroot = root->inh_target_child_roots[childRTindex]; /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo is created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals. */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); + if (IS_DUMMY_REL(childrel)) continue; - } - - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } if (relation_excluded_by_constraints(root, childrel, childRTE)) { @@ -1271,8 +1133,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -1280,32 +1140,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Assert(IS_SIMPLE_REL(rel)); /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, we will bubble up - * the indexes of partitioned relations that appear down in the tree, so - * that when we've created Paths for all the children, the root - * partitioned table's list will contain all such indexes. - */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - rel->partitioned_child_rels = list_make1_int(rti); - - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - - /* * If this is a partitioned baserel, set the consider_partitionwise_join * flag; currently, we only consider partitionwise joins with the baserel * if its targetlist doesn't contain a whole-row Var. @@ -1342,12 +1176,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; ListCell *parentvars; ListCell *childvars; - ListCell *lc; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1355,18 +1185,34 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo is created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); /* - * Copy/Modify targetlist. Even if this child is deemed empty, we need - * its targetlist in case it falls on nullable side in a child-join - * because of partitionwise join. + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals. + */ + if (IS_DUMMY_REL(childrel)) + continue; + + if (relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* + * This child need not be scanned, so we can omit it from the + * appendrel. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Copy/Modify targetlist. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -1396,144 +1242,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, false); childrel->has_eclass_joins = rel->has_eclass_joins; - /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* - * This child need not be scanned, so we can omit it from the - * appendrel. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - /* CE failed, so finish copying/modifying join quals. */ childrel->joininfo = (List *) adjust_appendrel_attrs(root, @@ -3070,6 +2778,7 @@ inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) subroot = root->inh_target_child_roots[appinfo->child_relid]; Assert(subroot->parse->resultRelation > 0); childrel = find_base_rel(root, appinfo->child_relid); + Assert(childrel != NULL); /* Ignore excluded/pruned children. */ if (IS_DUMMY_REL(childrel)) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 22ca4d999a..297e47b424 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -21,6 +21,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/prep.h" +#include "optimizer/tlist.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -47,6 +48,9 @@ static void try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, List *parent_restrictlist); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + int partidx); /* @@ -1377,6 +1381,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts); + if (child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts); + /* We should never try to join two overlapping sets of rels. */ Assert(!bms_overlap(child_rel1->relids, child_rel2->relids)); child_joinrelids = bms_union(child_rel1->relids, child_rel2->relids); @@ -1583,3 +1592,55 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx) +{ + RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + AppendRelInfo *appinfo = make_append_rel_info(parent, parentrte, + parent->tupdesc, + parentrte->relid, + parent->reltype, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index a66374094f..c628f3d9e7 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -616,64 +616,6 @@ create_lateral_join_info(PlannerInfo *root) bms_add_member(brel2->lateral_referencers, rti); } } - - /* - * Lastly, propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - */ - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - RangeTblEntry *brte = root->simple_rte_array[rti]; - - /* - * Skip empty slots. Also skip non-simple relations i.e. dead - * relations. - */ - if (brel == NULL || !IS_SIMPLE_REL(brel)) - continue; - - /* - * In the case of table inheritance, the parent RTE is directly linked - * to every child table via an AppendRelInfo. In the case of table - * partitioning, the inheritance hierarchy is expanded one level at a - * time rather than flattened. Therefore, an other member rel that is - * a partitioned table may have children of its own, and must - * therefore be marked with the appropriate lateral info so that those - * children eventually get marked also. - */ - Assert(brte); - if (brel->reloptkind == RELOPT_OTHER_MEMBER_REL && - (brte->rtekind != RTE_RELATION || - brte->relkind != RELKIND_PARTITIONED_TABLE)) - continue; - - if (brte->inh) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); - RelOptInfo *childrel; - - if (appinfo->parent_relid != rti) - continue; - childrel = root->simple_rel_array[appinfo->child_relid]; - Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - Assert(childrel->direct_lateral_relids == NULL); - childrel->direct_lateral_relids = brel->direct_lateral_relids; - Assert(childrel->lateral_relids == NULL); - childrel->lateral_relids = brel->lateral_relids; - Assert(childrel->lateral_referencers == NULL); - childrel->lateral_referencers = brel->lateral_referencers; - } - } - } } diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 4f68ad9907..825998ad51 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -258,6 +258,16 @@ query_planner(PlannerInfo *root, List *tlist, root->all_baserels = bms_add_member(root->all_baserels, brel->relid); } + /* + * Expand RT entries that represent inherited or partitioned tables. + * This will perform partition pruning on partitioned tables in the + * original range table and also recursively on any child partitioned + * tables that were added by the expansion of the original parent(s). + * As new entries are added to the range table, various arrays in the + * PlannerInfo will be expanded accordingly. + */ + expand_inherited_tables(root); + inherited_update = (parse->resultRelation && root->simple_rte_array[parse->resultRelation]->inh); @@ -369,7 +379,7 @@ adjust_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) * column, filling placeholder entries for dropped columns, etc., all of * which occurs with the child's TupleDesc. */ - tlist = preprocess_targetlist(subroot); + tlist = preprocess_targetlist(subroot, true); subroot->processed_tlist = tlist; /* Add any newly added Vars to the child RelOptInfo. */ diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index bea89dd8dd..648858237a 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -23,6 +23,7 @@ #include "access/sysattr.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -701,27 +702,24 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } /* * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). + * pullup (so that all non-inherited RTEs are present). */ preprocess_rowmarks(root); /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain base relations not joins, so it's OK (and marginally more - * efficient) to do it after checking for join RTEs. We must do it after - * pulling up subqueries, else we'd fail to handle inherited tables in - * subqueries. - */ - expand_inherited_tables(root); - - /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -1214,8 +1212,11 @@ inheritance_planner(PlannerInfo *root) */ root->unexpanded_tlist = list_copy(root->parse->targetList); - /* Do the scan/join planning. */ - tlist = preprocess_targetlist(root); + /* + * Do the scan/join planning. We haven't expanded inheritance yet, so + * pass false. + */ + tlist = preprocess_targetlist(root, false); root->processed_tlist = tlist; qp_extra.tlist = tlist; qp_extra.activeWindows = qp_extra.groupClause = NIL; @@ -1224,9 +1225,10 @@ inheritance_planner(PlannerInfo *root) /* * If it turned out during query planning that all the children are dummy * (pruned or excluded by constraints), no need to do the steps below. - * Let grouping_planner finish up the final path. + * For regular inheritance case, it's possible that we only need to modify + * the parent table. Let grouping_planner finish up the final path. */ - if (IS_DUMMY_REL(planned_rel)) + if (!parent_rte->inh || IS_DUMMY_REL(planned_rel)) { grouping_planner(root, false, planned_rel, 0.0); return; @@ -1573,14 +1575,19 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, */ if (planned_rel == NULL) { - tlist = preprocess_targetlist(root); + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root, false); /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation - * so that we can transfer its decoration (resnames etc) to the - * topmost tlist of the finished Plan. + * We are now mostly done hacking up the query's targetlist. More + * columns might be added during query_planner after inheritance + * expansion in make_one_rel, because some children may require + * different column than the parent for row mark handling; see + * add_rowmark_junk_columns(). Most of the remaining planning + * work will be done with the PathTarget representation of tlists, + * but save aside the full representation so that we can transfer + * its decoration (resnames etc) to the topmost tlist of the + * finished Plan. */ root->processed_tlist = tlist; } @@ -2356,7 +2363,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2381,7 +2388,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } @@ -6818,6 +6825,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -6919,6 +6930,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index fc7ba0dea2..baced8126c 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -325,6 +325,12 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) UPPERREL_FINAL, NULL))) add_rtes_to_flat_rtable(rel->subroot, true); } + /* + * A NULL rel also means an unplanned subquery rte, so apply + * flatten_unplanned_rtes. + */ + else + flatten_unplanned_rtes(glob, rte); } rti++; } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index f8bc2dd257..a55949c305 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -65,9 +65,13 @@ static List *expand_targetlist(List *tlist, int command_type, * * As a side effect, if there's an ON CONFLICT UPDATE clause, its targetlist * is also preprocessed (and updated in-place). + * + * inheritance_expanded specifies whether inheritance tables are expanded + * as of calling this function. Caller must specify it because that affects + * which row marking related junk columns get added to the targetlist. */ List * -preprocess_targetlist(PlannerInfo *root) +preprocess_targetlist(PlannerInfo *root, bool inheritance_expanded) { Query *parse = root->parse; int result_relation = parse->resultRelation; @@ -134,6 +138,15 @@ preprocess_targetlist(PlannerInfo *root) if (rc->rti != rc->prti) continue; + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which will change the parent row + * mark's allMarkTypes fields. + */ + if (rc->isParent && !inheritance_expanded) + continue; + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) { /* Need to fetch TID */ diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 5d59385ef7..ba8e88c672 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -34,15 +34,19 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" +#include "optimizer/var.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" +#include "partitioning/partprune.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/selfuncs.h" diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index be6ce11da4..06a39bb92d 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -31,10 +31,10 @@ typedef struct AppendRelInfo **appinfos; } adjust_appendrel_attrs_context; -static void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, adjust_appendrel_attrs_context *context); static Relids adjust_child_relids(Relids relids, int nappinfos, @@ -47,18 +47,20 @@ static List *adjust_inherited_tlist(List *tlist, * Build an AppendRelInfo for the parent-child pair */ AppendRelInfo * -make_append_rel_info(Relation parentrel, Relation childrel, - Index parentRTindex, Index childRTindex) +make_append_rel_info(RelOptInfo *parent, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childtype, + Index childRTindex) { AppendRelInfo *appinfo = makeNode(AppendRelInfo); - appinfo->parent_relid = parentRTindex; + appinfo->parent_relid = parent->relid; appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->rd_rel->reltype; - appinfo->child_reltype = childrel->rd_rel->reltype; - make_inh_translation_list(parentrel, childrel, childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = RelationGetRelid(parentrel); + appinfo->parent_reltype = parent->reltype; + appinfo->child_reltype = childtype; + make_inh_translation_list(parent->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; return appinfo; } @@ -71,14 +73,11 @@ make_append_rel_info(Relation parentrel, Relation childrel, * For paranoia's sake, we match type/collation as well as attribute name. */ static void -make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, - List **translated_vars) +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) { List *vars = NIL; - TupleDesc old_tupdesc = RelationGetDescr(oldrelation); - TupleDesc new_tupdesc = RelationGetDescr(newrelation); - Oid new_relid = RelationGetRelid(newrelation); int oldnatts = old_tupdesc->natts; int newnatts = new_tupdesc->natts; int old_attno; @@ -108,7 +107,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, * When we are generating the "translation list" for the parent table * of an inheritance set, no need to search for matches. */ - if (oldrelation == newrelation) + if (from_rel == to_rel) { vars = lappend(vars, makeVar(newvarno, (AttrNumber) (old_attno + 1), @@ -134,10 +133,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, { HeapTuple newtup; - newtup = SearchSysCacheAttName(new_relid, attname); + newtup = SearchSysCacheAttName(to_rel, attname); if (!newtup) elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; ReleaseSysCache(newtup); @@ -147,10 +146,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, /* Found it, check type and collation match */ if (atttypid != att->atttypid || atttypmod != att->atttypmod) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); if (attcollation != att->attcollation) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); vars = lappend(vars, makeVar(newvarno, (AttrNumber) (new_attno + 1), diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 243d61da8f..0412917fc1 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -38,19 +38,22 @@ #include "partitioning/partprune.h" #include "utils/rel.h" +static void expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti); static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); + Index rti, RelOptInfo *rel); static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p); +static RelOptInfo *build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); /* * expand_inherited_tables @@ -62,33 +65,134 @@ static void expand_single_inheritance_child(PlannerInfo *root, void expand_inherited_tables(PlannerInfo *root) { - Index nrtes; + int orig_rtable_size; Index rti; - ListCell *rl; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; /* - * expand_inherited_rtentry may add RTEs to parse->rtable. The function is + * expand_append_rtentry may add RTEs to parse->rtable. The function is * expected to recursively handle any RTEs that it creates with inh=true. * So just scan as far as the original end of the rtable list. */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) + for (rti = 1; rti < orig_rtable_size; rti++) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + if (rte->inh) + expand_append_rtentry(root, brel, rte, rti); + } +} + +/* + * expand_append_rtentry + * This initializes RelOptInfos for an appendrel's child relations, if + * any + * + * 'rel' is the appendrel parent, whose range table entry ('rte') has been + * marked to require adding children. An appendrel parent could either + * be a subquery (if we flattened UNION ALL query) or a table that's known + * to have inheritance children. The latter consists of both regular + * inheritance parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as an appendrel child. The original RTE is + * considered to represent the whole inheritance set. + */ +static void +expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, + Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_append_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_inherited_rtentry(root, rte, rti, rel); } } /* * expand_inherited_rtentry - * Check whether a rangetable entry represents an inheritance set. - * If so, add entries for all the child tables to the query's - * rangetable, and build AppendRelInfo nodes for all the child tables - * and add them to root->append_rel_list. If not, clear the entry's - * "inh" flag to prevent later code from looking for AppendRelInfos. + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. * * Note that the original RTE is considered to represent the whole * inheritance set. The first of the generated RTEs is an RTE for the same @@ -99,55 +203,33 @@ expand_inherited_tables(PlannerInfo *root) * regular inheritance, a parent RTE must always have at least two associated * AppendRelInfos: one corresponding to the parent table as a simple member of * inheritance set and one or more corresponding to the actual children. - * Since a partitioned table is not scanned, it might have only one associated - * AppendRelInfo. */ static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, + RelOptInfo *rel) { Oid parentOID; PlanRowMark *oldrc; - Relation oldrelation; - LOCKMODE lockmode; + LOCKMODE lockmode = rte->rellockmode; List *inhOIDs; ListCell *l; + int num_children; + int num_children_added = 0; - /* Does RT entry allow inheritance? */ - if (!rte->inh) - return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } - /* Fast path for common case of childless table */ + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); parentOID = rte->relid; - if (!has_subclass(parentOID)) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * The rewriter should already have obtained an appropriate lock on each - * relation named in the query. However, for each child relation we add - * to the query, we must obtain an appropriate lock, because this will be - * the first use of those relations in the parse/rewrite/plan pipeline. - * Child rels should use the same lockmode as their parent. - */ - lockmode = rte->rellockmode; /* Scan for all members of inheritance set, acquire needed locks */ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); /* * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite above has_subclass() check, if table - * once had a child but no longer does. + * case. This could happen despite has_subclass() check performed by + * subquery_planner, if table once had a child but no longer does. */ - if (list_length(inhOIDs) < 2) + num_children = list_length(inhOIDs); + if (num_children < 2) { /* Clear flag before returning */ rte->inh = false; @@ -155,216 +237,219 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } /* - * If parent relation is selected FOR UPDATE/SHARE, we need to mark its - * PlanRowMark as isParent = true, and generate a new PlanRowMark for each - * child. + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. */ oldrc = get_plan_rowmark(root->rowMarks, rti); - if (oldrc) - oldrc->isParent = true; + Assert(oldrc == NULL || oldrc->isParent); /* - * Must open the parent relation to examine its tupdesc. We need not lock - * it; we assume the rewriter already did. + * Must expand PlannerInfo arrays by num_children before we can add + * children. */ - oldrelation = heap_open(parentOID, NoLock); + expand_planner_arrays(root, num_children); - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) + foreach(l, inhOIDs) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); - - /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. - */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); - } - else - { - List *appinfos = NIL; + Oid childOID = lfirst_oid(l); + Relation newrelation; RangeTblEntry *childrte; Index childRTindex; + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. */ - foreach(l, inhOIDs) + if (RELATION_IS_OTHER_TEMP(newrelation)) { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = heap_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - heap_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - heap_close(newrelation, NoLock); + heap_close(newrelation, lockmode); + continue; } - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); + (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation, &childrte, + &childRTindex); + Assert(childrte != NULL); + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + Assert(childRTindex > 0); + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; } - heap_close(oldrelation, NoLock); + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } } /* * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) + Index parentRTindex, RelOptInfo *parentrel) { + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; - check_stack_depth(); - - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + expand_planner_arrays(root, bms_num_members(partindexes)); /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. */ - if (partdesc->nparts == 0) - { - parentrte->inh = false; - return; - } + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); - for (i = 0; i < partdesc->nparts; i++) + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) { - Oid childOID = partdesc->oids[i]; - Relation childrel; + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + RangeTblEntry *childrte; + Index childRTindex; - /* Open rel; we already have required locks */ - childrel = heap_open(childOID, NoLock); + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation, + &childrte, &childRTindex); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + + /* If the child is partitioned itself, expand it too. */ + if (childrel->part_scheme) + { + Assert(childrte->inh); expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); + childrel); + } + } - /* Close child relation, but keep locks */ - heap_close(childrel, NoLock); + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); } } /* - * expand_single_inheritance_child - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) + * The return value is the RelOptInfo that's added. * * PlanRowMarks still carry the top-parent's RTI, and the top-parent's * allMarkTypes field still accumulates values from all descendents. * * "parentrte" and "parentRTindex" are immediate parent's RTE and * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. */ -static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); Oid childOID = RelationGetRelid(childrel); RangeTblEntry *childrte; Index childRTindex; AppendRelInfo *appinfo; + RelOptInfo *childrelopt; /* * Build an RTE for the child, and attach to query's rangetable list. We * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh = false. Also, set requiredPerms to zero since - * all required permissions checks are done on the original RTE. Likewise, - * set the child's securityQuals to empty, because we only want to apply - * the parent's RLS conditions regardless of what RLS properties + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties * individual children may have. (This is an intentional choice to make * inherited RLS work like regular permissions checks.) The parent * securityQuals will be propagated to children along with other base @@ -374,49 +459,42 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childrte_p = childrte; childrte->relid = childOID; childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) - childrte->inh = true; - else - childrte->inh = false; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); childrte->requiredPerms = 0; childrte->securityQuals = NIL; parse->rtable = lappend(parse->rtable, childrte); childRTindex = list_length(parse->rtable); *childRTindex_p = childRTindex; - /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. - */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) - { - appinfo = make_append_rel_info(parentrel, childrel, - parentRTindex, childRTindex); - *appinfos = lappend(*appinfos, appinfo); + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + RelationGetDescr(childrel), + RelationGetRelid(childrel), + RelationGetForm(childrel)->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); } /* @@ -448,4 +526,251 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need to create one, + * because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_append_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; +} + +/* + * build_append_child_rel + * Build a RelOptInfo for child relation of an append rel + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields based on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turn out to be self-contradictory, + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + List *childquals; + ListCell *lc; + bool have_const_false_cq; + Index cq_min_security; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = false; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, parent->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual = (Node *) rinfo->clause; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, childqual, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might + * have securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals.) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except + * that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* + * not likely that we'd see constants here, so no + * check + */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, + false, + false, + security_level, + NULL, NULL, + NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* Set child's version of baserestrictinfo. */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; } diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 51d8faf2dd..6562d3f4ff 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -106,7 +106,7 @@ static void set_baserel_partition_key_exprs(Relation relation, */ void get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) + Bitmapset *updatedCols, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; @@ -444,11 +444,31 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, get_relation_foreign_keys(root, rel, relation, inhparent); /* - * Collect info about relation's partitioning scheme, if any. Only - * inheritance parents may be partitioned. + * Collect some additional information for inheritance parents. */ - if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - set_relation_partition_info(root, rel, relation); + if (inhparent) + { + /* + * We'll need the TupleDesc when initializing the child relation. + * A copy is being made because concurrent changes might drop + * the relcache entry. That's possible because ALTER TABLE + * child_table NO INHERIT parent_table only requires an + * AccessShareLock on parent_table. + */ + rel->tupdesc = CreateTupleDescCopy(RelationGetDescr(relation)); + rel->reltype = RelationGetForm(relation)->reltype; + + /* + * If partitioned, also save the information of partitioning scheme, + * and whether the query updates any of the partition key columns. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + set_relation_partition_info(root, rel, relation); + root->partColsUpdated |= has_partition_attrs(relation, updatedCols, + NULL); + } + } heap_close(relation, NoLock); @@ -1853,16 +1873,20 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; + + /* + * Since we must've taken a lock on the table, it's okay to simply copy + * the pointers to relcache data here. + */ + rel->part_oids = partdesc->oids; + rel->boundinfo = partdesc->boundinfo; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 678da0ce55..8ac73b4c34 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -131,6 +131,37 @@ setup_append_rel_array(PlannerInfo *root) } } +/* Expand the PlannerInfo arrays by add_size members and zero-init it. */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size = root->simple_rel_array_size + add_size; + + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + if (root->append_rel_array) + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + else + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + + /* Set the contents of just allocated memory to 0. */ + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + root->simple_rel_array_size = new_size; +} + /* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. @@ -239,7 +270,8 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte->relid, rte->inh, rte->updatedCols, + rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -280,52 +312,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); - /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. - */ - if (rte->inh) - { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); - - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; - } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); - } - return rel; } diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 901433c68c..9bb472fd98 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -46,6 +46,7 @@ #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" #include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/planner.h" #include "optimizer/predtest.h" @@ -437,17 +438,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -541,23 +548,20 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ @@ -565,6 +569,13 @@ prune_append_rel_partitions(RelOptInfo *rel) return NULL; /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. */ @@ -591,15 +602,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index a2adbfc3b8..0303a454ce 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -15,6 +15,7 @@ #define RELATION_H #include "access/sdir.h" +#include "access/tupdesc.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/params.h" @@ -726,6 +727,7 @@ typedef struct RelOptInfo int nparts; /* number of partitions */ struct PartitionBoundInfoData *boundinfo; /* Partition bounds */ List *partition_qual; /* partition constraint */ + Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ List **partexprs; /* Non-nullable partition key expressions. */ @@ -735,6 +737,12 @@ typedef struct RelOptInfo Index inh_root_parent; /* For otherrels, this is the RT index of * inheritance table mentioned in the query * from which this relation originated */ + /* + * Set only if this is an inheritance parent relation. This information + * is needed when initializing the planning info for children. + */ + TupleDesc tupdesc; /* A "copy" of the table's tuple desriptor */ + Oid reltype; /* Table's reltype */ } RelOptInfo; /* diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h index 2348c9c13a..bbed1fc73b 100644 --- a/src/include/optimizer/appendinfo.h +++ b/src/include/optimizer/appendinfo.h @@ -18,9 +18,10 @@ #include "nodes/relation.h" #include "utils/relcache.h" -extern AppendRelInfo *make_append_rel_info(Relation parentrel, - Relation childrel, - Index parentRTindex, Index childRTindex); +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parent, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childtype, + Index childRTindex); extern Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index bd905d3328..e7d0a20c54 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -261,6 +261,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index a1b23251a1..ce666b56ad 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -26,7 +26,7 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); + bool inhparent, Bitmapset *updatedCols, RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 62d45dd142..a329e87fc2 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -38,7 +38,8 @@ extern Expr *canonicalize_qual(Expr *qual, bool is_check); /* * prototypes for preptlist.c */ -extern List *preprocess_targetlist(PlannerInfo *root); +extern List *preprocess_targetlist(PlannerInfo *root, + bool inheritance_expanded); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h index ce9bc8d9fd..5c5d27d040 100644 --- a/src/include/partitioning/partprune.h +++ b/src/include/partitioning/partprune.h @@ -76,7 +76,7 @@ extern PartitionPruneInfo *make_partition_pruneinfo(PlannerInfo *root, List *subpaths, List *partitioned_rels, List *prunequal); -extern Relids prune_append_rel_partitions(RelOptInfo *rel); +extern Bitmapset *prune_append_rel_partitions(RelOptInfo *rel); extern Bitmapset *get_matching_partitions(PartitionPruneContext *context, List *pruning_steps); diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 6bc106831e..1450cef057 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) -- 2.11.0
v12-0005-Teach-planner-to-only-process-unpruned-partition.patch (text/plain, 6.9K) download From 0ac44556cc549ae51361b220cfade01ebdf76eda Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 15 Oct 2018 10:59:24 +0900 Subject: [PATCH v12 5/6] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/inherit.c | 1 + src/backend/optimizer/util/relnode.c | 3 +++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/relation.h | 4 ++++ 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 297e47b424..a0902b5525 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1424,6 +1424,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 648858237a..c7d07aab7f 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6817,7 +6817,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -6825,9 +6827,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -6908,7 +6908,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -6920,7 +6919,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -6930,9 +6931,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 0412917fc1..1a18f51e81 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -350,6 +350,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, /* Perform pruning. */ partindexes = prune_append_rel_partitions(parentrel); + parentrel->live_parts = partindexes; /* Must expand PlannerInfo arrays before we can add children. */ expand_planner_arrays(root, bms_num_members(partindexes)); diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 8ac73b4c34..74d1dcaa20 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1748,6 +1748,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 9bb472fd98..088b191bdd 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -441,29 +441,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 0303a454ce..a0a71fcd62 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -730,6 +730,10 @@ typedef struct RelOptInfo Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Indexes into part_rels of the non-NULL + * RelOptInfos of unpruned partitions; exists + * to avoid having to iterate over the entire + * part_rels array to filter NULL entries. */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.11.0
v12-0006-Do-not-lock-all-partitions-at-the-beginning.patch (text/plain, 1.8K) download From 12416be040e2ed00257dc1ee6553ea06adf8c0d1 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 12 Oct 2018 19:12:53 +0900 Subject: [PATCH v12 6/6] Do not lock all partitions at the beginning --- src/backend/optimizer/util/inherit.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 1a18f51e81..f798003aaa 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -317,10 +317,6 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, * * Partitions are added to the query in order in which they are found in * the parent's PartitionDesc. - * - * Note: even though only the unpruned partitions will be added to the - * resulting plan, this still locks *all* partitions via find_all_inheritors - * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, @@ -334,10 +330,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, parentrel->inh_root_parent : parentRTindex; - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - /* * Initialize partitioned_child_rels to contain this RT index. * @@ -373,8 +365,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, RangeTblEntry *childrte; Index childRTindex; - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); + newrelation = heap_open(childOID, lockmode); Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-10 09:12  David Rowley <david.rowley@2ndquadrant.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  2 siblings, 0 replies; 265+ messages in thread

From: David Rowley @ 2019-01-10 09:12 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers

On Thu, 10 Jan 2019 at 21:41, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> In the v13 that I will try to post tomorrow, I will have hopefully
> addressed David's and Imai-san's review comments.  Thank you both!

I'd been looking at v11's 0002 and started on 0003 too. I'll include
my notes so far if you're about to send a v13.


v11 0002

18. There's a missing case in the following code. I understand that
makeNode() will 0 the member here, but that does not stop the various
other initialisations that set the default value for the field.  Below
there's a missing case where parent != NULL && parent->rtekind !=
RTE_RELATION. You might be better just always zeroing the field below
"rel->partitioned_child_rels = NIL;"

+
+ /*
+ * For inheritance child relations, we also set inh_root_parent.
+ * Note that 'parent' might itself be a child (a sub-partitioned
+ * partition), in which case we simply use its value of
+ * inh_root_parent.
+ */
+ if (parent->rtekind == RTE_RELATION)
+ rel->inh_root_parent = parent->inh_root_parent > 0 ?
+ parent->inh_root_parent :
+ parent->relid;
  }
  else
+ {
  rel->top_parent_relids = NULL;
+ rel->inh_root_parent = 0;
+ }

19. Seems strange to have a patch that adds a new field that is
unused. I also don't quite understand yet why top_parent_relids can't
be used instead. I think I recall being confused about that before, so
maybe it's worth writing a comment to mention why it cannot be used.

v11 0003

20. This code looks wrong:

+ /*
+ * expand_inherited_tables may have proved that the relation is empty, so
+ * check if it's so.
+ */
+ else if (rte->inh && !IS_DUMMY_REL(rel))


Likely you'll want:

else if rte->inh)
{
if (IS_DUMMY_REL(rel))
return;
// other stuff
}

otherwise, you'll end up in the else clause when you shouldn't be.

21. is -> was

+ * The child rel's RelOptInfo is created during
+ * expand_inherited_tables().
  */
  childrel = find_base_rel(root, childRTindex);

since you're talking about something that already happened.

I'll continue looking at v12.

-- 
 David Rowley                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-10 17:56  Alvaro Herrera <alvherre@2ndquadrant.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  2 siblings, 2 replies; 265+ messages in thread

From: Alvaro Herrera @ 2019-01-10 17:56 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; David Rowley <david.rowley@2ndquadrant.com>; pgsql-hackers

On 2019-Jan-10, Amit Langote wrote:

> Here's v12, which is more or less same as v11 but with the order of
> patches switched so that the code movement patch is first.  Note that the
> attached v12-0001 contains no functional changes (but there's tiny note in
> the commit message mentioning the addition of a tiny function which is
> just old code).

Pushed 0001 with some minor tweaks, thanks.

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-10 21:47  David Rowley <david.rowley@2ndquadrant.com>
  parent: Alvaro Herrera <alvherre@2ndquadrant.com>
  1 sibling, 1 reply; 265+ messages in thread

From: David Rowley @ 2019-01-10 21:47 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Amit Langote <amitlangote09@gmail.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers

On Fri, 11 Jan 2019 at 06:56, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
> Pushed 0001 with some minor tweaks, thanks.

Thanks for pushing.  I had looked at 0001 last night and there wasn't
much to report, just:

v12 0001

1. I see you've moved translate_col_privs() out of prepunion.c into
appendinfo.c. This required making it an external function, but it's
only use is in inherit.c, so would it not be better to put it there
and keep it static?

2. The following two lines I think need to swap their order.

+#include "utils/rel.h"
+#include "utils/lsyscache.h"

Both are pretty minor details but thought I'd post them anyway.

-- 
 David Rowley                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-11 01:18  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Alvaro Herrera <alvherre@2ndquadrant.com>
  1 sibling, 0 replies; 265+ messages in thread

From: Amit Langote @ 2019-01-11 01:18 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; David Rowley <david.rowley@2ndquadrant.com>; pgsql-hackers

On 2019/01/11 2:56, Alvaro Herrera wrote:
> On 2019-Jan-10, Amit Langote wrote:
> 
>> Here's v12, which is more or less same as v11 but with the order of
>> patches switched so that the code movement patch is first.  Note that the
>> attached v12-0001 contains no functional changes (but there's tiny note in
>> the commit message mentioning the addition of a tiny function which is
>> just old code).
> 
> Pushed 0001 with some minor tweaks, thanks.

Thank you for the tweaks and committing.

Regards,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-11 02:07  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: David Rowley <david.rowley@2ndquadrant.com>
  0 siblings, 1 reply; 265+ messages in thread

From: Amit Langote @ 2019-01-11 02:07 UTC (permalink / raw)
  To: David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers

Sorry, I hadn't read this email before sending my earlier "thank you for
committing" email.

On 2019/01/11 6:47, David Rowley wrote:
> On Fri, 11 Jan 2019 at 06:56, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
>> Pushed 0001 with some minor tweaks, thanks.
> 
> Thanks for pushing.  I had looked at 0001 last night and there wasn't
> much to report, just:
> 
> v12 0001
> 
> 1. I see you've moved translate_col_privs() out of prepunion.c into
> appendinfo.c. This required making it an external function, but it's
> only use is in inherit.c, so would it not be better to put it there
> and keep it static?

Actually, I *was* a bit puzzled where to put it.  I tend to agree with you
now that it might be define it locally within inherit.c as it might not be
needed elsewhere.  Let's hear what Alvaro thinks.  I'm attaching a patch
anyway.

> 2. The following two lines I think need to swap their order.
> 
> +#include "utils/rel.h"
> +#include "utils/lsyscache.h"

Oops, fixed.

> Both are pretty minor details but thought I'd post them anyway.

Thank you for reporting.

Attached find the patch.

Regards,
Amit

Attachments:

b60c397599-fixups.patch (text/plain, 5.6K) download diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index d48e3a09b3..2f23e7bf49 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -15,13 +15,12 @@ #include "postgres.h" #include "access/htup_details.h" -#include "access/sysattr.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" #include "parser/parsetree.h" -#include "utils/rel.h" #include "utils/lsyscache.h" +#include "utils/rel.h" #include "utils/syscache.h" @@ -167,58 +166,6 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, } /* - * translate_col_privs - * Translate a bitmapset representing per-column privileges from the - * parent rel's attribute numbering to the child's. - * - * The only surprise here is that we don't translate a parent whole-row - * reference into a child whole-row reference. That would mean requiring - * permissions on all child columns, which is overly strict, since the - * query is really only going to reference the inherited columns. Instead - * we set the per-column bits for all inherited columns. - */ -Bitmapset * -translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars) -{ - Bitmapset *child_privs = NULL; - bool whole_row; - int attno; - ListCell *lc; - - /* System attributes have the same numbers in all tables */ - for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) - { - if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - attno - FirstLowInvalidHeapAttributeNumber); - } - - /* Check if parent has whole-row reference */ - whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, - parent_privs); - - /* And now translate the regular user attributes, using the vars list */ - attno = InvalidAttrNumber; - foreach(lc, translated_vars) - { - Var *var = lfirst_node(Var, lc); - - attno++; - if (var == NULL) /* ignore dropped columns */ - continue; - if (whole_row || - bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - var->varattno - FirstLowInvalidHeapAttributeNumber); - } - - return child_privs; -} - -/* * adjust_appendrel_attrs * Copy the specified query or expression and translate Vars referring to a * parent rel to refer to the corresponding child rel instead. We also diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 350e6afe27..db474acbc5 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -15,6 +15,7 @@ #include "postgres.h" #include "access/heapam.h" +#include "access/sysattr.h" #include "catalog/partition.h" #include "catalog/pg_inherits.h" #include "miscadmin.h" @@ -38,6 +39,8 @@ static void expand_single_inheritance_child(PlannerInfo *root, PlanRowMark *top_parentrc, Relation childrel, List **appinfos, RangeTblEntry **childrte_p, Index *childRTindex_p); +static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars); /* @@ -437,3 +440,55 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } } + +/* + * translate_col_privs + * Translate a bitmapset representing per-column privileges from the + * parent rel's attribute numbering to the child's. + * + * The only surprise here is that we don't translate a parent whole-row + * reference into a child whole-row reference. That would mean requiring + * permissions on all child columns, which is overly strict, since the + * query is really only going to reference the inherited columns. Instead + * we set the per-column bits for all inherited columns. + */ +static Bitmapset * +translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars) +{ + Bitmapset *child_privs = NULL; + bool whole_row; + int attno; + ListCell *lc; + + /* System attributes have the same numbers in all tables */ + for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) + { + if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + attno - FirstLowInvalidHeapAttributeNumber); + } + + /* Check if parent has whole-row reference */ + whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, + parent_privs); + + /* And now translate the regular user attributes, using the vars list */ + attno = InvalidAttrNumber; + foreach(lc, translated_vars) + { + Var *var = lfirst_node(Var, lc); + + attno++; + if (var == NULL) /* ignore dropped columns */ + continue; + if (whole_row || + bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + var->varattno - FirstLowInvalidHeapAttributeNumber); + } + + return child_privs; +} diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h index 16705da780..56f7192d71 100644 --- a/src/include/optimizer/appendinfo.h +++ b/src/include/optimizer/appendinfo.h @@ -20,8 +20,6 @@ extern AppendRelInfo *make_append_rel_info(Relation parentrel, Relation childrel, Index parentRTindex, Index childRTindex); -extern Bitmapset *translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos);
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-11 04:01  David Rowley <david.rowley@2ndquadrant.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  2 siblings, 1 reply; 265+ messages in thread

From: David Rowley @ 2019-01-11 04:01 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers

On Thu, 10 Jan 2019 at 21:41, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> Here's v12, which is more or less same as v11 but with the order of
> patches switched so that the code movement patch is first.  Note that the
> attached v12-0001 contains no functional changes (but there's tiny note in
> the commit message mentioning the addition of a tiny function which is
> just old code).

A few more comments based on reading v12.

v12 0002:

1. Missing braces around the else clause. (Should be consistent with
the "if" above)

+ if (!has_live_children)
+ {
+ /*
+ * All children were excluded by constraints, so mark the relation
+ * ass dummy.  We must do this in this phase so that the rel's
+ * dummy-ness is visible when we generate paths for other rels.
+ */
+ set_dummy_rel_pathlist(rel);
+ }
+ else
+ /*
+ * Set a non-zero value here to cope with the caller's requirement
+ * that non-dummy relations are actually not empty.  We don't try to
+ * be accurate here, because we're not going to create a path that
+ * combines the children outputs.
+ */
+ rel->rows = 1;

v12 0004:

2. I wonder if there's a better way, instead of doing this:

+ if (child_rel1 == NULL)
+ child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts);
+ if (child_rel2 == NULL)
+ child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts);

maybe add some logic in populate_joinrel_with_paths() to allow NULL
rels to mean dummy rels.  There's a bit of a problem there as the
joinrel has already been created by that time, but perhaps
populate_joinrel_with_paths is a better place to decide if the dummy
rel needs to be built or not.

3. I wonder if there's a better way to handle what
build_dummy_partition_rel() does. I see the child's relid to the
parent's relid and makes up a fake AppendRelInfo and puts it in the
parent's slot.  What's going to happen when the parent is not the
top-level parent? It'll already have a AppendRelInfo set.

I had thought something like the following could break this, but of
course, it does not since we build the dummy rel for the pruned
sub_parent2, so we don't hit the NULL relation case when doing the
next level. i.e we only make dummies for the top-level, never dummys
of joinrels.

Does that not mean that the if (parent->top_parent_relids) will always
be false in build_dummy_partition_rel() and it'll only ever get
rtekind == RTE_RELATION?

drop table if exists parent;
create table parent (id int, a int, b text, c float) partition by range (a);
create table sub_parent1 (b text, c float, a int, id int) partition by
range (a);
create table sub_parent2 (c float, b text, id int, a int) partition by
range (a);
alter table parent attach partition sub_parent1 for values from (0) to (10);
alter table parent attach partition sub_parent2 for values from (10) to (20);

create table child11 (id int, b text, c float, a int);
create table child12 (id int, b text, c float, a int);
create table child21 (id int, b text, c float, a int);
create table child22 (id int, b text, c float, a int);

alter table sub_parent1 attach partition child11 for values from (0) to (5);
alter table sub_parent1 attach partition child12 for values from (5) to (10);
alter table sub_parent2 attach partition child21 for values from (10) to (15);
alter table sub_parent2 attach partition child22 for values from (15) to (20);

insert into parent values(0,1,'test',100.0);

select * from parent p1 inner join parent p2 on p1.a=p2.a where p1.id < 10;

4. How are dummy rels handled in grouping_planner()?

I see you have this:

- if (IS_DUMMY_REL(planned_rel))
+ if (!parent_rte->inh || IS_DUMMY_REL(planned_rel))
  {
  grouping_planner(root, false, planned_rel, 0.0);
  return;

With the above example I tried to see how it was handled by doing:

postgres=# update parent set c = c where a = 333;
server closed the connection unexpectedly
        This probably means the server terminated abnormally
        before or while processing the request.

I didn't look into what's causing the crash.

5. Wondering why you removed the if (childOID != parentOID) from:

- if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation))
- {
- heap_close(newrelation, lockmode);
- continue;
- }

Isn't that releasing the only lock we hold on the rel defined in the query?

I tested with:

-- session 1
create temp table a1(a int);
create temp table a2(a int) inherits(a1);

-- session 2
select oid::regclass from pg_class where relname = 'a1';
     oid
--------------
 pg_temp_3.a1
(1 row)

explain select * from pg_temp_3.a1;
WARNING:  you don't own a lock of type AccessShareLock
                QUERY PLAN
------------------------------------------
 Result  (cost=0.00..0.00 rows=0 width=4)
   One-Time Filter: false
(2 rows)

6. expand_planner_arrays() zeros a portion of the append_rel_array
even if it just palloc0'd the array. While it's not a bug, it is
repeat work.  It should be okay to move the Memset() up to the
repalloc().

7. I see get_relation_info() grew an extra parameter.  Would it now be
better just to pass rte instead of doing;

get_relation_info(root, rte->relid, rte->inh, rte->updatedCols,
  rel);

-- 
 David Rowley                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-01-11 06:10  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: David Rowley <david.rowley@2ndquadrant.com>
  0 siblings, 1 reply; 265+ messages in thread

From: Imai, Yoshikazu @ 2019-01-11 06:10 UTC (permalink / raw)
  To: David Rowley <david.rowley@2ndquadrant.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Hi David,

On Thu, Jan 10, 2019 at 4:02 PM, David Rowley wrote:
> 3. I wonder if there's a better way to handle what
> build_dummy_partition_rel() does. I see the child's relid to the
> parent's relid and makes up a fake AppendRelInfo and puts it in the
> parent's slot.  What's going to happen when the parent is not the
> top-level parent? It'll already have a AppendRelInfo set.
... 
> 
> select * from parent p1 inner join parent p2 on p1.a=p2.a where p1.id < 10;

I think there is a mistake in the select SQL.
"p1.id < 10" doesn't prune any partition because tables are partitioned by
column "a" in your definition. Isn't it?

> Does that not mean that the if (parent->top_parent_relids) will always
> be false in build_dummy_partition_rel() and it'll only ever get
> rtekind == RTE_RELATION?

At least, I checked if (parent->top_parent_relids) can be true if I execute
below SQL.

select * from parent p1 inner join parent p2 on p1.a=p2.a where p1.id < 15;

I couldn't check other points you mentioned, but I also think
build_dummy_partition_rel() needs more consideration because I felt it has
complicated logic when I was checking around here.


Amit,
I also realized there are some mistakes in the comments around this function.

+ * build_dummy_partition_rel
+ *		Build a RelOptInfo and AppendRelInfo for a pruned partition 
s/and AppendRelInfo/and an AppendRelInfo/

+	 * Now we'll need a (no-op) AppendRelInfo for parent, because we're
+	 * setting the dummy partition's relid to be same as the parent's.
s/a \(no-op\) AppendRelInfo/an \(no-op\) AppendRelInfo/

--
Yoshikazu Imai



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-11 06:24  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 1 reply; 265+ messages in thread

From: Amit Langote @ 2019-01-11 06:24 UTC (permalink / raw)
  To: David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers

On 2019/01/11 11:07, Amit Langote wrote:
> On 2019/01/11 6:47, David Rowley wrote:
>> On Fri, 11 Jan 2019 at 06:56, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
>>> Pushed 0001 with some minor tweaks, thanks.
>>
>> Thanks for pushing.  I had looked at 0001 last night and there wasn't
>> much to report, just:
>>
>> v12 0001
>>
>> 1. I see you've moved translate_col_privs() out of prepunion.c into
>> appendinfo.c. This required making it an external function, but it's
>> only use is in inherit.c, so would it not be better to put it there
>> and keep it static?
> 
> Actually, I *was* a bit puzzled where to put it.  I tend to agree with you
> now that it might be define it locally within inherit.c as it might not be
> needed elsewhere.  Let's hear what Alvaro thinks.  I'm attaching a patch
> anyway.
> 
>> 2. The following two lines I think need to swap their order.
>>
>> +#include "utils/rel.h"
>> +#include "utils/lsyscache.h"
> 
> Oops, fixed.
> 
>> Both are pretty minor details but thought I'd post them anyway.
> 
> Thank you for reporting.
> 
> Attached find the patch.

Looking around a bit more, I started thinking even build_child_join_sjinfo
doesn't belong in appendinfo.c (just to be clear, it was defined in
prepunion.c before this commit), so maybe we should move it to joinrels.c
and instead export adjust_child_relids that's required by it from
appendinfo.c.  There's already adjust_child_relids_multilevel in
appendinfo.h, so having adjust_child_relids next to it isn't too bad.  At
least not as bad as appendinfo.c exporting build_child_join_sjinfo for
joinrels.c to use.

I have updated the patch.

Thanks,
Amit

Attachments:

b60c397599-fixups-v2.patch (text/plain, 10.9K) download diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 38eeb23d81..db18feccfe 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -46,6 +46,9 @@ static void try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, List *parent_restrictlist); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, + SpecialJoinInfo *parent_sjinfo, + Relids left_relids, Relids right_relids); /* @@ -1582,3 +1585,45 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * Construct the SpecialJoinInfo for a child-join by translating + * SpecialJoinInfo for the join between parents. left_relids and right_relids + * are the relids of left and right side of the join respectively. + */ +static SpecialJoinInfo * +build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, + Relids left_relids, Relids right_relids) +{ + SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo); + AppendRelInfo **left_appinfos; + int left_nappinfos; + AppendRelInfo **right_appinfos; + int right_nappinfos; + + memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo)); + left_appinfos = find_appinfos_by_relids(root, left_relids, + &left_nappinfos); + right_appinfos = find_appinfos_by_relids(root, right_relids, + &right_nappinfos); + + sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand, + left_nappinfos, left_appinfos); + sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand, + right_nappinfos, + right_appinfos); + sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand, + left_nappinfos, left_appinfos); + sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand, + right_nappinfos, + right_appinfos); + sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root, + (Node *) sjinfo->semi_rhs_exprs, + right_nappinfos, + right_appinfos); + + pfree(left_appinfos); + pfree(right_appinfos); + + return sjinfo; +} diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index d48e3a09b3..37fbc3a117 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -15,13 +15,12 @@ #include "postgres.h" #include "access/htup_details.h" -#include "access/sysattr.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" #include "parser/parsetree.h" -#include "utils/rel.h" #include "utils/lsyscache.h" +#include "utils/rel.h" #include "utils/syscache.h" @@ -38,8 +37,6 @@ static void make_inh_translation_list(Relation oldrelation, List **translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, adjust_appendrel_attrs_context *context); -static Relids adjust_child_relids(Relids relids, int nappinfos, - AppendRelInfo **appinfos); static List *adjust_inherited_tlist(List *tlist, AppendRelInfo *context); @@ -167,58 +164,6 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, } /* - * translate_col_privs - * Translate a bitmapset representing per-column privileges from the - * parent rel's attribute numbering to the child's. - * - * The only surprise here is that we don't translate a parent whole-row - * reference into a child whole-row reference. That would mean requiring - * permissions on all child columns, which is overly strict, since the - * query is really only going to reference the inherited columns. Instead - * we set the per-column bits for all inherited columns. - */ -Bitmapset * -translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars) -{ - Bitmapset *child_privs = NULL; - bool whole_row; - int attno; - ListCell *lc; - - /* System attributes have the same numbers in all tables */ - for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) - { - if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - attno - FirstLowInvalidHeapAttributeNumber); - } - - /* Check if parent has whole-row reference */ - whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, - parent_privs); - - /* And now translate the regular user attributes, using the vars list */ - attno = InvalidAttrNumber; - foreach(lc, translated_vars) - { - Var *var = lfirst_node(Var, lc); - - attno++; - if (var == NULL) /* ignore dropped columns */ - continue; - if (whole_row || - bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - var->varattno - FirstLowInvalidHeapAttributeNumber); - } - - return child_privs; -} - -/* * adjust_appendrel_attrs * Copy the specified query or expression and translate Vars referring to a * parent rel to refer to the corresponding child rel instead. We also @@ -531,7 +476,7 @@ adjust_appendrel_attrs_mutator(Node *node, * Substitute child relids for parent relids in a Relid set. The array of * appinfos specifies the substitutions to be performed. */ -static Relids +Relids adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos) { Bitmapset *result = NULL; @@ -754,48 +699,6 @@ adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, } /* - * Construct the SpecialJoinInfo for a child-join by translating - * SpecialJoinInfo for the join between parents. left_relids and right_relids - * are the relids of left and right side of the join respectively. - */ -SpecialJoinInfo * -build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, - Relids left_relids, Relids right_relids) -{ - SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo); - AppendRelInfo **left_appinfos; - int left_nappinfos; - AppendRelInfo **right_appinfos; - int right_nappinfos; - - memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo)); - left_appinfos = find_appinfos_by_relids(root, left_relids, - &left_nappinfos); - right_appinfos = find_appinfos_by_relids(root, right_relids, - &right_nappinfos); - - sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand, - left_nappinfos, left_appinfos); - sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand, - right_nappinfos, - right_appinfos); - sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand, - left_nappinfos, left_appinfos); - sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand, - right_nappinfos, - right_appinfos); - sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root, - (Node *) sjinfo->semi_rhs_exprs, - right_nappinfos, - right_appinfos); - - pfree(left_appinfos); - pfree(right_appinfos); - - return sjinfo; -} - -/* * find_appinfos_by_relids * Find AppendRelInfo structures for all relations specified by relids. * diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 350e6afe27..db474acbc5 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -15,6 +15,7 @@ #include "postgres.h" #include "access/heapam.h" +#include "access/sysattr.h" #include "catalog/partition.h" #include "catalog/pg_inherits.h" #include "miscadmin.h" @@ -38,6 +39,8 @@ static void expand_single_inheritance_child(PlannerInfo *root, PlanRowMark *top_parentrc, Relation childrel, List **appinfos, RangeTblEntry **childrte_p, Index *childRTindex_p); +static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars); /* @@ -437,3 +440,55 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } } + +/* + * translate_col_privs + * Translate a bitmapset representing per-column privileges from the + * parent rel's attribute numbering to the child's. + * + * The only surprise here is that we don't translate a parent whole-row + * reference into a child whole-row reference. That would mean requiring + * permissions on all child columns, which is overly strict, since the + * query is really only going to reference the inherited columns. Instead + * we set the per-column bits for all inherited columns. + */ +static Bitmapset * +translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars) +{ + Bitmapset *child_privs = NULL; + bool whole_row; + int attno; + ListCell *lc; + + /* System attributes have the same numbers in all tables */ + for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) + { + if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + attno - FirstLowInvalidHeapAttributeNumber); + } + + /* Check if parent has whole-row reference */ + whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, + parent_privs); + + /* And now translate the regular user attributes, using the vars list */ + attno = InvalidAttrNumber; + foreach(lc, translated_vars) + { + Var *var = lfirst_node(Var, lc); + + attno++; + if (var == NULL) /* ignore dropped columns */ + continue; + if (whole_row || + bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + var->varattno - FirstLowInvalidHeapAttributeNumber); + } + + return child_privs; +} diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h index 16705da780..156a0e077d 100644 --- a/src/include/optimizer/appendinfo.h +++ b/src/include/optimizer/appendinfo.h @@ -20,21 +20,15 @@ extern AppendRelInfo *make_append_rel_info(Relation parentrel, Relation childrel, Index parentRTindex, Index childRTindex); -extern Bitmapset *translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); - extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, Relids child_relids, Relids top_parent_relids); - extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos); - -extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, - SpecialJoinInfo *parent_sjinfo, - Relids left_relids, Relids right_relids); +extern Relids adjust_child_relids(Relids relids, int nappinfos, + AppendRelInfo **appinfos); extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, Relids child_relids, Relids top_parent_relids);
^ permalink raw reply [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-01-11 06:52  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  0 siblings, 0 replies; 265+ messages in thread

From: Imai, Yoshikazu @ 2019-01-11 06:52 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; David Rowley <david.rowley@2ndquadrant.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On Thu, Jan 10, 2019 at 6:10 PM, Imai, Yoshikazu wrote:
> > Does that not mean that the if (parent->top_parent_relids) will always
> > be false in build_dummy_partition_rel() and it'll only ever get
> > rtekind == RTE_RELATION?
> 
> At least, I checked if (parent->top_parent_relids) can be true if I
> execute below SQL.
> 
> select * from parent p1 inner join parent p2 on p1.a=p2.a where p1.id < 15;

Sorry, I also made mistake. I was executed:
select * from parent p1 inner join parent p2 on p1.a=p2.a where p1.a < 15;

--
Yoshikazu Imai


> -----Original Message-----
> From: Imai, Yoshikazu [mailto:imai.yoshikazu@jp.fujitsu.com]
> Sent: Friday, January 11, 2019 3:10 PM
> To: 'David Rowley' <david.rowley@2ndquadrant.com>; Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp>
> Cc: Amit Langote <amitlangote09@gmail.com>; Alvaro Herrera
> <alvherre@2ndquadrant.com>; Pg Hackers <pgsql-hackers@postgresql.org>
> Subject: RE: speeding up planning with partitions
> 
> Hi David,
> 
> On Thu, Jan 10, 2019 at 4:02 PM, David Rowley wrote:
> > 3. I wonder if there's a better way to handle what
> > build_dummy_partition_rel() does. I see the child's relid to the
> > parent's relid and makes up a fake AppendRelInfo and puts it in the
> > parent's slot.  What's going to happen when the parent is not the
> > top-level parent? It'll already have a AppendRelInfo set.
> ...
> >
> > select * from parent p1 inner join parent p2 on p1.a=p2.a where p1.id
> > < 10;
> 
> I think there is a mistake in the select SQL.
> "p1.id < 10" doesn't prune any partition because tables are partitioned
> by column "a" in your definition. Isn't it?
> 
> > Does that not mean that the if (parent->top_parent_relids) will always
> > be false in build_dummy_partition_rel() and it'll only ever get
> > rtekind == RTE_RELATION?
> 
> At least, I checked if (parent->top_parent_relids) can be true if I
> execute below SQL.
> 
> select * from parent p1 inner join parent p2 on p1.a=p2.a where p1.id
> < 15;
> 
> I couldn't check other points you mentioned, but I also think
> build_dummy_partition_rel() needs more consideration because I felt it
> has complicated logic when I was checking around here.
> 
> 
> Amit,
> I also realized there are some mistakes in the comments around this
> function.
> 
> + * build_dummy_partition_rel
> + *		Build a RelOptInfo and AppendRelInfo for a pruned
> partition
> s/and AppendRelInfo/and an AppendRelInfo/
> 
> +	 * Now we'll need a (no-op) AppendRelInfo for parent, because
> we're
> +	 * setting the dummy partition's relid to be same as the parent's.
> s/a \(no-op\) AppendRelInfo/an \(no-op\) AppendRelInfo/
> 
> --
> Yoshikazu Imai



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-11 13:00  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: David Rowley <david.rowley@2ndquadrant.com>
  0 siblings, 2 replies; 265+ messages in thread

From: Amit Langote @ 2019-01-11 13:00 UTC (permalink / raw)
  To: David Rowley <david.rowley@2ndquadrant.com>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Thanks for reviewing, David, Imai-san.  Replying to all reviews (up to and
including David's comments earlier today) with this one email so that I
can attach the finished patch here.

On 2019/01/09 9:09, David Rowley wrote:
> On Tue, 8 Jan 2019 at 19:30, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> Rebased due to changed copyright year in prepunion.c.  Also realized that
>> the new files added by patch 0004 still had 2018 in them.
> 
> I've made a pass over 0001. There's probably enough for you to look at
> while I look at 0002 and the others.
> 
> 0001
> 
> 1. In your doc changes, just below a paragraph that you removed,
> there's a paragraph starting "Both of these behaviors are likely to be
> changed in a future release". This needs to be fixed since you've
> removed the first of the two reasons.

OK, I've fixed the sentence and moved it to the previous paragraph.

> 2. This part should be left alone.
> 
> -    technique similar to partition pruning.  While it is primarily used
> -    for partitioning implemented using the legacy inheritance method, it can be
> -    used for other purposes, including with declarative partitioning.
> +    technique similar to partition pruning.  It is primarily used
> +    for partitioning implemented using the legacy inheritance method.
> 
> Looking at set_inherit_target_rel_sizes(), constraint exclusion still
> is applied to partitions, it's just applied after pruning, according
> to:

OK, I've restored the sentence.

> 3. add_child_rel_equivalences().  You're replacing parent EMs with
> their child equivalent, but only when the eclass has no volatile
> functions. Is this really what you want? I think this would misbehave
> if we ever allowed: UPDATE ... SET .. ORDER BY, of which there's a
> legitimate use case of wanting to reduce the chances of deadlocks
> caused by non-deterministic UPDATE order.  Or if you think skipping
> the volatile eclasses is fine today, then I think the comment you've
> added to add_child_rel_equivalences should mention that.

To be honest, I hadn't considered this aspect before.

I think it would be OK to create a new copy of the EC even if it's a
volatile one as we're creating an entirely new one for the child's
planning.  Maybe, this will also ensure that someone who will work in the
future on implementing UPDATE SET ORDER BY, they won't have to fiddle with
this code.

> 4. Do you think it's okay that add_child_rel_equivalences() does not
> update the ec_relids when removing the member?

That's an oversight.  Fixed by making add_child_rel_equivalences do this:

+            cur_ec->ec_relids = bms_difference(cur_ec->ec_relids,
+                                               parent_rel->relids);
+            cur_ec->ec_relids = bms_add_members(cur_ec->ec_relids,
+                                                child_rel->relids);

> 
> UPDATE: I see you're likely leaving this alone since you're only doing
> a shallow copy of the eclasses in
> adjust_inherited_target_child_root(). It seems like a pretty bad idea
> to do a shallow copy there.

So, you're talking about this code:

    /*
     * Child root should get its own copy of ECs, because they'll be modified
     * to replace parent EC expressions by child expressions in
     * add_child_rel_equivalences.
     */
    subroot->eq_classes = NIL;
    foreach(lc, root->eq_classes)
    {
        EquivalenceClass *ec = lfirst(lc);
        EquivalenceClass *new_ec = makeNode(EquivalenceClass);

        memcpy(new_ec, ec, sizeof(EquivalenceClass));
        new_ec->ec_members = list_copy(ec->ec_members);
        subroot->eq_classes = lappend(subroot->eq_classes, new_ec);
    }

Can you say what you think is wrong with this way of making a copy of the ECs?

> 
> 5. What's CE?
> 
> + /* CE failed, so finish copying/modifying join quals. */

Constraint exclusion.  It seems I needed to fix comments around here.

> 6. Typo:
> 
> + * ass dummy.  We must do this in this phase so that the rel's
> 
> ass -> as

Oops!  Fixed.

> 
> 7. There's no accumulation going on here:
> 
> + /*
> + * Accumulate size information from each live child.
> + */
> + Assert(childrel->rows > 0);

Removed the comment.

> 
> 8. Any point in this? We're about to loop again anyway...
> 
> + /*
> + * If child is dummy, ignore it.
> + */
> + if (IS_DUMMY_REL(childrel))
> + continue;
> + }

Removed this code.

> 9. It's a bit confusing to mention SELECT in this comment. The Assert
> ensures it's an UPDATE or DELETE.
> 
> + /*
> + * For UPDATE/DELETE queries, the top parent can only ever be a table.
> + * As a contrast, it could be a UNION ALL subquery in the case of SELECT.
> + */
> + Assert(root->parse->commandType == CMD_UPDATE ||
> +    root->parse->commandType == CMD_DELETE);

I guess we don't need the 2nd sentence.  Removed.

> 10. I'd say the subroot assignment can go after the IS_DUMMY_REL
> check. Keeping that loop as tight as possible for pruned rels seems
> like a good idea.
> 
> + subroot = root->inh_target_child_roots[appinfo->child_relid];
> + Assert(subroot->parse->resultRelation > 0);
> + childrel = find_base_rel(root, appinfo->child_relid);
> +
> + /* Ignore excluded/pruned children. */
> + if (IS_DUMMY_REL(childrel))
> + continue;

Agreed, done.

> 11. I don't think you should reuse the childrel variable here:
> 
> + childrel->reloptkind = RELOPT_BASEREL;
> +
> + Assert(subroot->join_rel_list == NIL);
> + Assert(subroot->join_rel_hash == NULL);
> +
> + /* Perform join planning and save the resulting RelOptInfo. */
> + childrel = make_rel_from_joinlist(subroot, translated_joinlist);
> +
> + /*
> + * Remember this child target rel.  inheritance_planner will perform
> + * the remaining steps of planning for each child relation separately.
> + * Specifically, it will call grouping_planner on every
> + * RelOptInfo contained in the inh_target_child_rels list, each of
> + * which represents the source of tuples to be modified for a given
> + * target child rel.
> + */
> + root->inh_target_child_joinrels =
> + lappend(root->inh_target_child_joinrels, childrel);

OK, I've added a new variable called childjoinrel.

> 12. The following comment makes less sense now that you've modified
> the previous paragraph:
> 
> + * Fortunately, the UPDATE/DELETE target can never be the nullable side of an
> + * outer join, so it's OK to generate the plan this way.
> 
> This text used to refer to:
> 
> but target inheritance has to be expanded at
>  * the top.  The reason is that for UPDATE, each target relation needs a
>  * different targetlist matching its own column set.  Fortunately,
>  * the UPDATE/DELETE target can never be the nullable side of an outer join,
>  * so it's OK to generate the plan this way.
> 
> you no longer describe plan as being expanded from the top rather than
> at the bottom, which IMO is what "this way" refers to.

To be honest, I never quite understood what that line really means, which
is why I kept it around.  What I do know though is that, even with the
patched, we still generate subpaths for each child whose targetlist
patches the child, so I think the part about "this way" that prompted
someone to write that line still remains.  Does that make sense to you?

> 13. "tree is" -> "tree are" (references is plural)
> 
> + * references in the join tree to the original target relation that's the
> + * root parent of the inheritance tree is replaced by each of its

Fixed.

> 14. Any reason to move this line from its original location?
> 
>   Assert(parse->commandType != CMD_INSERT);
> + parent_rte = planner_rt_fetch(top_parentRTindex, root);
> 
> Previously it was assigned just before it was needed and there's a
> fast path out after where you moved it to and where it was.

Next patch in the series needs it moved, but no reason for this patch to
move it.  Put it back where it was.

> 15. relation_excluded_by_constraints(), the switch
> (constraint_exclusion), you could consider turning that into
> 
> if (constraint_exclusion == CONSTRAINT_EXCLUSION_OFF)
>     return false;
> /*
>  * When constraint_exclusion is set to 'partition' we only handle
>  * OTHER_MEMBER_RELs.
>  */
> else if (constraint_exclusion == CONSTRAINT_EXCLUSION_PARTITION &&
>          rel->reloptkind != RELOPT_OTHER_MEMBER_REL)
>     return false;
> 
> When I wrote that code I was trying my best to make the complex rules
> as simple as possible by separating them out.  The rules have become
> quite simple after your change, so it probably does not warrant having
> the switch.

OK, done.

> 16. I think the following comment needs to explain how large this
> array is and what indexes it.  The current comment would have you
> think there are only enough elements to store PlannerInfos for child
> rels and leaves you guessing about what they're indexed by.
> 
> + /*
> + * PlannerInfos corresponding to each inheritance child targets.
> + * Content of each PlannerInfo is same as the parent PlannerInfo, except
> + * for the parse tree which is a translated copy of the parent's parse
> + * tree.
> + */
> + struct PlannerInfo **inh_target_child_roots;

I've added those details to the comment.

> 
> 17. I'm getting an assert failure in add_paths_to_append_rel() for
> Assert(parallel_workers > 0) during the partition_join tests.

I guess that was due to not using the correct root in
inherit_target_rel_pathlists.  Fixed.

On 2019/01/10 18:12, David Rowley wrote:>
> I'd been looking at v11's 0002 and started on 0003 too. I'll include
> my notes so far if you're about to send a v13.
>
>
> v11 0002
>
> 18. There's a missing case in the following code. I understand that
> makeNode() will 0 the member here, but that does not stop the various
> other initialisations that set the default value for the field.  Below
> there's a missing case where parent != NULL && parent->rtekind !=
> RTE_RELATION. You might be better just always zeroing the field below
> "rel->partitioned_child_rels = NIL;"
>
> +
> + /*
> + * For inheritance child relations, we also set inh_root_parent.
> + * Note that 'parent' might itself be a child (a sub-partitioned
> + * partition), in which case we simply use its value of
> + * inh_root_parent.
> + */
> + if (parent->rtekind == RTE_RELATION)
> + rel->inh_root_parent = parent->inh_root_parent > 0 ?
> + parent->inh_root_parent :
> + parent->relid;
>   }
>   else
> + {
>   rel->top_parent_relids = NULL;
> + rel->inh_root_parent = 0;
> + }

Okay, done.  Actually, also did the same for top_parent_relids.

> 19. Seems strange to have a patch that adds a new field that is
> unused. I also don't quite understand yet why top_parent_relids can't
> be used instead. I think I recall being confused about that before, so
> maybe it's worth writing a comment to mention why it cannot be used.

See if this updated comment makes it any clearer:

    /*
     * For inheritance children, this is the RT index of inheritance table
     * mentioned in the query from which this relation originated.
     * top_parent_relids cannot be used for this, because if the inheritance
     * root table is itself under UNION ALL, top_parent_relids contains the
     * RT index of UNION ALL parent subquery.
     */

This is its own patch, because it was thought it could be useful to
another thread which has since stalled:

https://www.postgresql.org/message-id/be766794-eb16-b798-52ec-1f786b26b61b%40lab.ntt.co.jp

> v11 0003
>
> 20. This code looks wrong:
>
> + /*
> + * expand_inherited_tables may have proved that the relation is empty, so
> + * check if it's so.
> + */
> + else if (rte->inh && !IS_DUMMY_REL(rel))
>
>
> Likely you'll want:
>
> else if rte->inh)
> {
> if (IS_DUMMY_REL(rel))
> return;
> // other stuff
> }
>
> otherwise, you'll end up in the else clause when you shouldn't be.

OK, done that way.

> 21. is -> was
>
> + * The child rel's RelOptInfo is created during
> + * expand_inherited_tables().
>   */
>   childrel = find_base_rel(root, childRTindex);
>
> since you're talking about something that already happened.

OK, fixed.

On 2019/01/11 13:01, David Rowley wrote:>
> A few more comments based on reading v12.
>
> v12 0002:
>
> 1. Missing braces around the else clause. (Should be consistent with
> the "if" above)
>
> + if (!has_live_children)
> + {
> + /*
> + * All children were excluded by constraints, so mark the relation
> + * ass dummy.  We must do this in this phase so that the rel's
> + * dummy-ness is visible when we generate paths for other rels.
> + */
> + set_dummy_rel_pathlist(rel);
> + }
> + else
> + /*
> + * Set a non-zero value here to cope with the caller's requirement
> + * that non-dummy relations are actually not empty.  We don't try to
> + * be accurate here, because we're not going to create a path that
> + * combines the children outputs.
> + */
> + rel->rows = 1;

Agreed, done.

> v12 0004:
>
> 2. I wonder if there's a better way, instead of doing this:
>
> + if (child_rel1 == NULL)
> + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts);
> + if (child_rel2 == NULL)
> + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts);
>
> maybe add some logic in populate_joinrel_with_paths() to allow NULL
> rels to mean dummy rels.  There's a bit of a problem there as the
> joinrel has already been created by that time, but perhaps
> populate_joinrel_with_paths is a better place to decide if the dummy
> rel needs to be built or not.

Hmm, I'd thought about that, but concluded that I shouldn't mix that work
with this refactoring project.  We can try to hack the join planning code
later, but until then build_dummy_partition_rel can keep things working.
Once we have a working solution that works without having to create this
dummy RelOptInfo, we can remove build_dummy_partition_rel.

> 3. I wonder if there's a better way to handle what
> build_dummy_partition_rel() does. I see the child's relid to the
> parent's relid and makes up a fake AppendRelInfo and puts it in the
> parent's slot.  What's going to happen when the parent is not the
> top-level parent? It'll already have a AppendRelInfo set.

Yeah, the parent's AppendRelInfo would already be present and it won't be
replaced:

    if (root->append_rel_array[parent->relid] == NULL)
    {
        AppendRelInfo *appinfo = make_append_rel_info(parent, parentrte,
                                                      parent->tupdesc,
                                                      parentrte->relid,
                                                      parent->reltype,
                                                      parent->relid);

        root->append_rel_array[parent->relid] = appinfo;
    }

Now you'll say why the discrepancy? For the top-level parent's dummy
children, appinfo generated is actually no-op, because it's generated
using the above code.  But for an intermediate parent's dummy parent's
there already exists a "real" appinfo.  It doesn't make much difference as
far as I can tell, because the appinfo is not used for anything significant.

> I had thought something like the following could break this, but of
> course, it does not since we build the dummy rel for the pruned
> sub_parent2, so we don't hit the NULL relation case when doing the
> next level. i.e we only make dummies for the top-level, never dummys
> of joinrels.
>
> Does that not mean that the if (parent->top_parent_relids) will always
> be false in build_dummy_partition_rel() and it'll only ever get
> rtekind == RTE_RELATION?

Well, parentrte->rtekind should always be RTE_RELATION in
build_dummy_partition_rel, because partitionwise join is considered only
for partitioned tables and joinrels resulting from joining partitioned tables.

parent->top_parent_relids might be non-NULL if it's an intermediate parent.

> 4. How are dummy rels handled in grouping_planner()?
>
> I see you have this:
>
> - if (IS_DUMMY_REL(planned_rel))
> + if (!parent_rte->inh || IS_DUMMY_REL(planned_rel))
>   {
>   grouping_planner(root, false, planned_rel, 0.0);
>   return;
>
> With the above example I tried to see how it was handled by doing:
>
> postgres=# update parent set c = c where a = 333;
> server closed the connection unexpectedly
>         This probably means the server terminated abnormally
>         before or while processing the request.
>
> I didn't look into what's causing the crash.

I tried your example, but it didn't crash for me:

explain update parent set c = c where a = 333;
                     QUERY PLAN
────────────────────────────────────────────────────
 Update on parent  (cost=0.00..0.00 rows=0 width=0)
   ->  Result  (cost=0.00..0.00 rows=0 width=54)
         One-Time Filter: false
(3 rows)


> 5. Wondering why you removed the if (childOID != parentOID) from:
>
> - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation))
> - {
> - heap_close(newrelation, lockmode);
> - continue;
> - }
>
> Isn't that releasing the only lock we hold on the rel defined in the query?

I think I mistakenly removed it.  Added it back.

> 6. expand_planner_arrays() zeros a portion of the append_rel_array
> even if it just palloc0'd the array. While it's not a bug, it is
> repeat work.  It should be okay to move the Memset() up to the
> repalloc().

Done.  Also moved other MemSets to their respective repallocs.

> 7. I see get_relation_info() grew an extra parameter.  Would it now be
> better just to pass rte instead of doing;
>
> get_relation_info(root, rte->relid, rte->inh, rte->updatedCols,
>   rel);
>

OK, done.

On 2019/01/10 15:37, Imai, Yoshikazu wrote:>
> I also have some comments on 0001, set_inherit_target_rel_sizes().
>
> In set_inherit_target_rel_sizes():
>
> Some codes are placed not the same order as set_append_rel_size().
>
> 0001: at line 325-326,
> +	ListCell   *l;
> +	bool		has_live_children;
>
> In set_append_rel_size(), "has_live_children" is above of the "ListCell *l";

OK, moved to look like set_append_rel_size.

> 0001: at line 582-603
> +		if (IS_DUMMY_REL(childrel))
> +			continue;
> +
> ...
> +		Assert(childrel->rows > 0);
> +
> +		/* We have at least one live child. */
> +		has_live_children = true;
>
> In set_append_rel_size(),
> +		/* We have at least one live child. */
> +		has_live_children = true;
> is directly under of
> +		if (IS_DUMMY_REL(childrel))
> +			continue;

OK, done.

> 0001: at line 606-622,
> +	if (!has_live_children)
> +	{
> +		/*
> +		 * All children were excluded by constraints, so mark the relation
> +		 * ass dummy.  We must do this in this phase so that the rel's
> +		 * dummy-ness is visible when we generate paths for other rels.
> +		 */
> +		set_dummy_rel_pathlist(rel);
> +	}
> +	else
> +		/*
> +		 * Set a non-zero value here to cope with the caller's requirement
> +		 * that non-dummy relations are actually not empty.  We don't try to
> +		 * be accurate here, because we're not going to create a path that
> +		 * combines the children outputs.
> +		 */
> +		rel->rows = 1;
>
> In set_append_rel_size(), a condition of if clause is not
> !has_live_children but
> has_live_children.
>
> I also noticed there isn't else block while there is if block.

Things that set_inherit_target_rel_sizes and set_append_rel_size need to
do, respectively, are different, so the code looks different.  Anyway,
I've switched the above if else blocks' code so that it looks like this:

if (has_live_children)
    rel->rows = 1;
else
    set_dummy_rel_pathlist(rel);

Here are the updated patches.  I've also attached the patch I posted
earlier today here as 0001-Some-fixups-for-b60c397599-v2.patch.

Thanks you again.

Regards,
Amit

Attachments: download all (6 files, .tar.gz)

0001-Some-fixups-for-b60c397599-v2.patch (text/plain, 11.4K) download From d9b349c42eab901680ec755239ff45bb3c97ba6a Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 11 Jan 2019 11:11:30 +0900 Subject: [PATCH v13 1/6] Some fixups for b60c397599 (v2) --- src/backend/optimizer/path/joinrels.c | 45 ++++++++++++++ src/backend/optimizer/util/appendinfo.c | 101 +------------------------------- src/backend/optimizer/util/inherit.c | 55 +++++++++++++++++ src/include/optimizer/appendinfo.h | 10 +--- 4 files changed, 104 insertions(+), 107 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 38eeb23d81..db18feccfe 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -46,6 +46,9 @@ static void try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, List *parent_restrictlist); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, + SpecialJoinInfo *parent_sjinfo, + Relids left_relids, Relids right_relids); /* @@ -1582,3 +1585,45 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * Construct the SpecialJoinInfo for a child-join by translating + * SpecialJoinInfo for the join between parents. left_relids and right_relids + * are the relids of left and right side of the join respectively. + */ +static SpecialJoinInfo * +build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, + Relids left_relids, Relids right_relids) +{ + SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo); + AppendRelInfo **left_appinfos; + int left_nappinfos; + AppendRelInfo **right_appinfos; + int right_nappinfos; + + memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo)); + left_appinfos = find_appinfos_by_relids(root, left_relids, + &left_nappinfos); + right_appinfos = find_appinfos_by_relids(root, right_relids, + &right_nappinfos); + + sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand, + left_nappinfos, left_appinfos); + sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand, + right_nappinfos, + right_appinfos); + sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand, + left_nappinfos, left_appinfos); + sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand, + right_nappinfos, + right_appinfos); + sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root, + (Node *) sjinfo->semi_rhs_exprs, + right_nappinfos, + right_appinfos); + + pfree(left_appinfos); + pfree(right_appinfos); + + return sjinfo; +} diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index d48e3a09b3..37fbc3a117 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -15,13 +15,12 @@ #include "postgres.h" #include "access/htup_details.h" -#include "access/sysattr.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" #include "parser/parsetree.h" -#include "utils/rel.h" #include "utils/lsyscache.h" +#include "utils/rel.h" #include "utils/syscache.h" @@ -38,8 +37,6 @@ static void make_inh_translation_list(Relation oldrelation, List **translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, adjust_appendrel_attrs_context *context); -static Relids adjust_child_relids(Relids relids, int nappinfos, - AppendRelInfo **appinfos); static List *adjust_inherited_tlist(List *tlist, AppendRelInfo *context); @@ -167,58 +164,6 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, } /* - * translate_col_privs - * Translate a bitmapset representing per-column privileges from the - * parent rel's attribute numbering to the child's. - * - * The only surprise here is that we don't translate a parent whole-row - * reference into a child whole-row reference. That would mean requiring - * permissions on all child columns, which is overly strict, since the - * query is really only going to reference the inherited columns. Instead - * we set the per-column bits for all inherited columns. - */ -Bitmapset * -translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars) -{ - Bitmapset *child_privs = NULL; - bool whole_row; - int attno; - ListCell *lc; - - /* System attributes have the same numbers in all tables */ - for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) - { - if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - attno - FirstLowInvalidHeapAttributeNumber); - } - - /* Check if parent has whole-row reference */ - whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, - parent_privs); - - /* And now translate the regular user attributes, using the vars list */ - attno = InvalidAttrNumber; - foreach(lc, translated_vars) - { - Var *var = lfirst_node(Var, lc); - - attno++; - if (var == NULL) /* ignore dropped columns */ - continue; - if (whole_row || - bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - var->varattno - FirstLowInvalidHeapAttributeNumber); - } - - return child_privs; -} - -/* * adjust_appendrel_attrs * Copy the specified query or expression and translate Vars referring to a * parent rel to refer to the corresponding child rel instead. We also @@ -531,7 +476,7 @@ adjust_appendrel_attrs_mutator(Node *node, * Substitute child relids for parent relids in a Relid set. The array of * appinfos specifies the substitutions to be performed. */ -static Relids +Relids adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos) { Bitmapset *result = NULL; @@ -754,48 +699,6 @@ adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, } /* - * Construct the SpecialJoinInfo for a child-join by translating - * SpecialJoinInfo for the join between parents. left_relids and right_relids - * are the relids of left and right side of the join respectively. - */ -SpecialJoinInfo * -build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, - Relids left_relids, Relids right_relids) -{ - SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo); - AppendRelInfo **left_appinfos; - int left_nappinfos; - AppendRelInfo **right_appinfos; - int right_nappinfos; - - memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo)); - left_appinfos = find_appinfos_by_relids(root, left_relids, - &left_nappinfos); - right_appinfos = find_appinfos_by_relids(root, right_relids, - &right_nappinfos); - - sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand, - left_nappinfos, left_appinfos); - sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand, - right_nappinfos, - right_appinfos); - sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand, - left_nappinfos, left_appinfos); - sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand, - right_nappinfos, - right_appinfos); - sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root, - (Node *) sjinfo->semi_rhs_exprs, - right_nappinfos, - right_appinfos); - - pfree(left_appinfos); - pfree(right_appinfos); - - return sjinfo; -} - -/* * find_appinfos_by_relids * Find AppendRelInfo structures for all relations specified by relids. * diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 350e6afe27..db474acbc5 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -15,6 +15,7 @@ #include "postgres.h" #include "access/heapam.h" +#include "access/sysattr.h" #include "catalog/partition.h" #include "catalog/pg_inherits.h" #include "miscadmin.h" @@ -38,6 +39,8 @@ static void expand_single_inheritance_child(PlannerInfo *root, PlanRowMark *top_parentrc, Relation childrel, List **appinfos, RangeTblEntry **childrte_p, Index *childRTindex_p); +static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars); /* @@ -437,3 +440,55 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } } + +/* + * translate_col_privs + * Translate a bitmapset representing per-column privileges from the + * parent rel's attribute numbering to the child's. + * + * The only surprise here is that we don't translate a parent whole-row + * reference into a child whole-row reference. That would mean requiring + * permissions on all child columns, which is overly strict, since the + * query is really only going to reference the inherited columns. Instead + * we set the per-column bits for all inherited columns. + */ +static Bitmapset * +translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars) +{ + Bitmapset *child_privs = NULL; + bool whole_row; + int attno; + ListCell *lc; + + /* System attributes have the same numbers in all tables */ + for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) + { + if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + attno - FirstLowInvalidHeapAttributeNumber); + } + + /* Check if parent has whole-row reference */ + whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, + parent_privs); + + /* And now translate the regular user attributes, using the vars list */ + attno = InvalidAttrNumber; + foreach(lc, translated_vars) + { + Var *var = lfirst_node(Var, lc); + + attno++; + if (var == NULL) /* ignore dropped columns */ + continue; + if (whole_row || + bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + var->varattno - FirstLowInvalidHeapAttributeNumber); + } + + return child_privs; +} diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h index 16705da780..156a0e077d 100644 --- a/src/include/optimizer/appendinfo.h +++ b/src/include/optimizer/appendinfo.h @@ -20,21 +20,15 @@ extern AppendRelInfo *make_append_rel_info(Relation parentrel, Relation childrel, Index parentRTindex, Index childRTindex); -extern Bitmapset *translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); - extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, Relids child_relids, Relids top_parent_relids); - extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos); - -extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, - SpecialJoinInfo *parent_sjinfo, - Relids left_relids, Relids right_relids); +extern Relids adjust_child_relids(Relids relids, int nappinfos, + AppendRelInfo **appinfos); extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, Relids child_relids, Relids top_parent_relids); -- 2.11.0
v13-0002-Overhaul-inheritance-update-delete-planning.patch (text/plain, 72.7K) download From 5eb9e0aa4d9e7a6f3eb7a2e9a380ebd28b821169 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 11:18:12 +0900 Subject: [PATCH v13 2/6] Overhaul inheritance update/delete planning With the current method, inheritance_planner, which handles the planning for update/delete commands targeting inheritance trees, performs the *whole* planning for each child table. That involves translating the query tree to set a given child table in place of the original inheritance root table as the query's target table, followed by calling grouping_planner on the translated query, which performs query planning and additional steps needed to apply correct target list based on the child target table. That's inefficient for two reasons: 1. partprune.c cannot be used to perform partition pruning, because it can only be invoked from query_planner. With the current approach, query_planner only sees the individual partitions in the query tree it receives, not the parent partitioned table. This leaves each partition to be pruned using constraint exclusion. (constraint exclusion cannon prune hash partitions which means UPDATE/DELETE don't support hash partition pruning.) 2. Repeated invocation of query_planner results in allocating large amounts of memory, especially if there are many child tables. Also, it involves repeatedly performing the same processing, such as jointree processing. This commit addresses both of the issues by rewriting portions of inheritance_planner and adding some supporting functionality to allpath.c. With the new implementation, inheritance_planner calls query_planner only once at the beginning with the original unmodified query, which creates the access paths of individual relations after pruning any unnecessary partitions. For each target child relation, it also creates a sub-PartitionInfo containing translated version of the query and a targetlist suitable for the child. Child PlannerInfos are saved in the top PlannerInfo for using later. If the query involves join against the target relation, join paths are created for each target child relation by replacing the original target table in the join tree by a given child table. Join relations (RelOptInfos thereof) for all target child relations are collected in a global list in the top PlannerInfo. After creating the join paths for all target child relations, inheritance_planner calls grouping_planner() on each child join relation using the previously created child PlannerInfo to finish up the paths such that they produce query's top-level target list expanded according to a given child relation's descriptor. grouping_planner()'s interface is modified so that we can pass it what's called a 'planned_rel', a RelOptInfo that already contains the necessary paths needed to produce its output. This removes some existing code in inheritance_planner that dealt with any subquery RTEs in the query. The rationale of that code was that the subquery RTEs may change during each iteration of planning (that is, for different children), so different iterations better use different copies of those RTEs. That was handled by making fresh copies of those RTEs for each iteration of planning which were appended to the range table, accompanied by modifying all expressions and auxiliary structures that referenced the original subquery RTEs to instead reference the copies (that is, change the varnos). This copying meant we would end up adding S * N new entries to the original range table by the time we got to the last unpruned child, where S is the number of subquery RTEs in the original query and N the number of unpruned children. Since with the new code we perform planning just once, I think we don't need this special handling. Actually, there is a regression test output change due to no longer having copies of subquery RTEs (see the diff of partition_join.out file.) Also we no longer need the hack in relation_excluded_by_constraints() involving setting PlannerInfo.inhTargetKind to enable constraint exclusion for target child relation, which is no longer needed. Constraint exclusion now runs during query_planner step described above. Regression test output change in partition_join.out is due to the fact that we no longer create duplicates of subquery RTEs in child range tables. --- doc/src/sgml/ddl.sgml | 17 +- src/backend/nodes/outfuncs.c | 2 +- src/backend/optimizer/path/allpaths.c | 579 +++++++++++++++++++++++++-- src/backend/optimizer/path/equivclass.c | 57 ++- src/backend/optimizer/plan/createplan.c | 10 - src/backend/optimizer/plan/planmain.c | 167 +++++++- src/backend/optimizer/plan/planner.c | 441 ++++++-------------- src/backend/optimizer/prep/prepjointree.c | 1 - src/backend/optimizer/util/appendinfo.c | 33 +- src/backend/optimizer/util/plancat.c | 73 +--- src/include/nodes/relation.h | 37 +- src/include/optimizer/paths.h | 3 +- src/test/regress/expected/partition_join.out | 4 +- 13 files changed, 958 insertions(+), 466 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 85e4358988..1bb5ca6b08 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -4439,24 +4439,11 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <note> <para> - Currently, pruning of partitions during the planning of an - <command>UPDATE</command> or <command>DELETE</command> command is - implemented using the constraint exclusion method (however, it is - controlled by the <literal>enable_partition_pruning</literal> rather than - <literal>constraint_exclusion</literal>) &mdash; see the following section - for details and caveats that apply. - </para> - - <para> Execution-time partition pruning currently only occurs for the <literal>Append</literal> and <literal>MergeAppend</literal> node types. It is not yet implemented for the <literal>ModifyTable</literal> node - type. - </para> - - <para> - Both of these behaviors are likely to be changed in a future release - of <productname>PostgreSQL</productname>. + type, but that is likely to be changed in a future release of + <productname>PostgreSQL</productname>. </para> </note> </sect2> diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 0fde876c77..158da68925 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2210,7 +2210,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); WRITE_UINT_FIELD(qual_security_level); - WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind); WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasDeletedRTEs); @@ -2221,6 +2220,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_BITMAPSET_FIELD(curOuterRels); WRITE_NODE_FIELD(curOuterParams); WRITE_BOOL_FIELD(partColsUpdated); + WRITE_BOOL_FIELD(inherited_update); } static void diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index bc389b52e4..35dba9c7ce 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -38,6 +38,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" @@ -92,8 +93,12 @@ static void set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); +static void set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte); static void set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); +static void set_inherit_target_rel_pathlists(PlannerInfo *root, + RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, @@ -120,6 +125,8 @@ static void set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); +static void inheritance_make_rel_from_joinlist(PlannerInfo *root, + List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, pushdown_safety_info *safetyInfo); static bool recurse_pushdown_safe(Node *setOp, Query *topquery, @@ -150,27 +157,6 @@ make_one_rel(PlannerInfo *root, List *joinlist) Index rti; double total_pages; - /* - * Construct the all_baserels Relids set. - */ - root->all_baserels = NULL; - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - - /* there may be empty slots corresponding to non-baserel RTEs */ - if (brel == NULL) - continue; - - Assert(brel->relid == rti); /* sanity check on array */ - - /* ignore RTEs that are "other rels" */ - if (brel->reloptkind != RELOPT_BASEREL) - continue; - - root->all_baserels = bms_add_member(root->all_baserels, brel->relid); - } - /* Mark base rels as to whether we care about fast-start plans */ set_base_rel_consider_startup(root); @@ -218,13 +204,34 @@ make_one_rel(PlannerInfo *root, List *joinlist) /* * Generate access paths for the entire join tree. + * + * For UPDATE/DELETE on an inheritance parent, join paths should be + * generated for each child result rel separately. */ - rel = make_rel_from_joinlist(root, joinlist); + if (root->parse->resultRelation && + root->simple_rte_array[root->parse->resultRelation]->inh) + { + inheritance_make_rel_from_joinlist(root, joinlist); - /* - * The result should join all and only the query's base rels. - */ - Assert(bms_equal(rel->relids, root->all_baserels)); + /* + * Return the RelOptInfo of original target relation, although this + * doesn't really contain the final path. inheritance_planner + * from where we got here will generate the final path, but it will + * do so by iterative over child subroots, not through this + * RelOptInfo. + */ + rel = find_base_rel(root, root->parse->resultRelation); + } + else + { + rel = make_rel_from_joinlist(root, joinlist); + + /* + * The result should join all and only the query's base rels. + */ + Assert(bms_equal(rel->relids, root->all_baserels)); + + } return rel; } @@ -308,9 +315,10 @@ set_base_rel_sizes(PlannerInfo *root) * If parallelism is allowable for this query in general, see whether * it's allowable for this rel in particular. We have to do this * before set_rel_size(), because (a) if this rel is an inheritance - * parent, set_append_rel_size() will use and perhaps change the rel's - * consider_parallel flag, and (b) for some RTE types, set_rel_size() - * goes ahead and makes paths immediately. + * parent, set_append_rel_size() or set_inherit_target_rel_sizes() + * will use and perhaps change the rel's consider_parallel flag, and + * (b) for some RTE types, set_rel_size() goes ahead and makes paths + * immediately. */ if (root->glob->parallelModeOK) set_rel_consider_parallel(root, rel, rte); @@ -374,8 +382,15 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_size(root, rel, rti, rte); + /* + * If it's a target relation, set the sizes of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (rti == root->parse->resultRelation) + set_inherit_target_rel_sizes(root, rel, rti, rte); + else + set_append_rel_size(root, rel, rti, rte); } else { @@ -459,14 +474,26 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { + bool inherited_update = false; + if (IS_DUMMY_REL(rel)) { /* We already proved the relation empty, so nothing more to do */ } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_pathlist(root, rel, rti, rte); + /* + * If it's a target relation, set the pathlists of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (root->inherited_update && root->parse->resultRelation == rti) + { + inherited_update = true; + set_inherit_target_rel_pathlists(root, rel, rti, rte); + } + else + set_append_rel_pathlist(root, rel, rti, rte); } else { @@ -541,8 +568,12 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, if (set_rel_pathlist_hook) (*set_rel_pathlist_hook) (root, rel, rti, rte); - /* Now find the cheapest of the paths for this rel */ - set_cheapest(rel); + /* + * Now find the cheapest of the paths for this rel, unless it's an + * inheritance parent and this is an update/delete operation. + */ + if (!inherited_update) + set_cheapest(rel); #ifdef OPTIMIZER_DEBUG debug_print_rel(root, rel); @@ -904,6 +935,347 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) } /* + * set_inherit_target_rel_sizes + * Set size estimates for the child target relations + * + * The passed-in rel represents the target relation of the query that is + * known to have inheritance children. This is very much like + * set_append_rel_size, except it doesn't set the size estimates for the + * passed-in rel itself, because we don't need to "append" the children + * in this case. + */ +static void +set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + bool has_live_children; + ListCell *l; + Relids live_children = NULL; + bool did_pruning = false; + + /* Guard against stack overflow due to overly deep inheritance tree. */ + check_stack_depth(); + + Assert(IS_SIMPLE_REL(rel)); + + /* + * If the partitioned relation has any baserestrictinfo quals then we + * attempt to use these quals to prune away partitions that cannot + * possibly contain any tuples matching these quals. In this case we'll + * store the relids of all partitions which could possibly contain a + * matching tuple, and skip anything else in the loop below. + */ + if (enable_partition_pruning && + rte->relkind == RELKIND_PARTITIONED_TABLE && + rel->baserestrictinfo != NIL) + { + live_children = prune_append_rel_partitions(rel); + did_pruning = true; + } + + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + List *childquals; + Index cq_min_security; + bool have_const_false_cq; + PlannerInfo *subroot; + ListCell *lc; + List *translated_exprs, + *child_target_exprs; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + subroot = root->inh_target_child_roots[childRTindex]; + + /* + * The child rel's RelOptInfo was already created during + * add_base_rels_to_query. + */ + childrel = find_base_rel(root, childRTindex); + Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = NIL; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, + (Node *) rinfo->clause, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + /* track minimum security level among child quals */ + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might have + * securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals, see expand_inherited_rtentry().) Pull any such + * securityQuals up into the baserestrictinfo for the child. This is + * similar to process_security_barrier_quals() for the parent rel, + * except that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* not likely that we'd see constants here, so no check */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, false, false, + security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* + * OK, we've got all the baserestrictinfo quals for this child. + */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) + { + /* This partition was pruned; skip it. */ + set_dummy_rel_pathlist(childrel); + continue; + } + + if (relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* This child is excluded; skip it. */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Add missing Vars to child's reltarget. + * + * add_inherit_target_child_root() would've added only those that are + * needed to be present in the top-level tlist (or ones that + * preprocess_targetlist thinks are needed to be in the tlist.) We + * may need other attributes such as those contained in WHERE clauses, + * which are already computed for the parent during + * deconstruct_jointree processing of the original query (child's + * query never goes through deconstruct_jointree.) + */ + translated_exprs = (List *) + adjust_appendrel_attrs(root, + (Node *) rel->reltarget->exprs, + 1, &appinfo); + child_target_exprs = childrel->reltarget->exprs; + foreach(lc, translated_exprs) + { + Expr *expr = lfirst(lc); + + if (!list_member(child_target_exprs, expr)) + child_target_exprs = lappend(child_target_exprs, expr); + } + + /* + * Also, We have to find any ECs containing parent's expressions and + * *replace* them with their copies containing child expressions. + */ + if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) + add_child_rel_equivalences(subroot, appinfo, rel, childrel, true); + childrel->has_eclass_joins = rel->has_eclass_joins; + + /* Translate join quals. */ + childrel->joininfo = (List *) + adjust_appendrel_attrs(subroot, + (Node *) rel->joininfo, + 1, &appinfo); + + /* + * If parallelism is allowable for this query in general, see whether + * it's allowable for this childrel in particular. For consistency, + * do this before calling set_rel_size() for the child. + */ + if (root->glob->parallelModeOK) + set_rel_consider_parallel(subroot, childrel, childRTE); + + /* + * Compute the child's size using possibly modified subroot. + */ + set_rel_size(subroot, childrel, childRTindex, childRTE); + + /* If the child itself is partitioned it may turn into a dummy rel. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* We have at least one live child. */ + has_live_children = true; + + Assert(childrel->rows > 0); + } + + if (has_live_children) + { + /* + * Set a non-zero value here to cope with the caller's requirement + * that non-dummy relations are actually not empty. We don't try to + * be accurate here, because we're not going to create a path that + * combines the children outputs. + */ + rel->rows = 1; + } + else + { + /* + * All children were excluded by constraints, so mark the relation + * ass dummy. We must do this in this phase so that the rel's + * dummy-ness is visible when we generate paths for other rels. + */ + set_dummy_rel_pathlist(rel); + } +} + +/* + * set_inherit_target_rel_pathlists + * Build access paths for the child target relations + * + * Similar to set_append_rel_pathlist, except that we build paths of the + * children, but don't build an Append path. + */ +static void +set_inherit_target_rel_pathlists(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + ListCell *l; + + /* Nothing to do if all the children were excluded. */ + if (IS_DUMMY_REL(rel)) + return; + + /* Generate access paths for each of the children of passed-in rel */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + PlannerInfo *subroot; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + /* Re-locate the child RTE and RelOptInfo */ + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + childrel = root->simple_rel_array[childRTindex]; + subroot = root->inh_target_child_roots[childRTindex]; + /* Transfer the value from main root to subroot. */ + subroot->total_table_pages = root->total_table_pages; + + /* + * If set_append_rel_size() decided the parent appendrel was + * parallel-unsafe at some point after visiting this child rel, we + * need to propagate the unsafety marking down to the child, so that + * we don't generate useless partial paths for it. + */ + if (!rel->consider_parallel) + childrel->consider_parallel = false; + + /* + * Compute the child's access paths. + */ + set_rel_pathlist(subroot, childrel, childRTindex, childRTE); + } +} + +/* * set_append_rel_size * Set size estimates for a simple "append relation" * @@ -1046,7 +1418,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, * EquivalenceClass data structures. */ if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) - add_child_rel_equivalences(root, appinfo, rel, childrel); + add_child_rel_equivalences(root, appinfo, rel, childrel, + false); childrel->has_eclass_joins = rel->has_eclass_joins; /* @@ -2625,6 +2998,140 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows) } /* + * inheritance_make_rel_from_joinlist + * Perform join planning for all non-dummy leaf inheritance children + * in their role as an UPDATE/DELETE query's target relation + * + * If a child relation is a partitioned table, its children are processed in + * turn by recursively calling this function. + */ +static void +inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; +#ifdef USE_ASSERT_CHECKING + Relids all_baserels; +#endif + + /* For UPDATE/DELETE queries, the top parent can only ever be a table. */ + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + Assert(planner_rt_fetch(resultRelation, root)->rtekind == RTE_RELATION); + + /* Nothing to do. */ + if (IS_DUMMY_REL(find_base_rel(root, resultRelation))) + return; + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + PlannerInfo *subroot; + RelOptInfo *childrel; + RelOptInfo *childjoinrel; + List *translated_joinlist; + + if (appinfo->parent_relid != resultRelation) + continue; + + childrel = find_base_rel(root, appinfo->child_relid); + + /* Ignore excluded/pruned children. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* Add this child. */ + root->inh_target_child_rels = lappend_int(root->inh_target_child_rels, + appinfo->child_relid); + + /* Perform join planning with child subroot. */ + subroot = root->inh_target_child_roots[appinfo->child_relid]; + Assert(subroot->parse->resultRelation > 0); + + /* + * Modify joinlist such that relations joined to the top parent rel + * appear to be joined to the child rel instead. Do the same for + * any SpecialJoinInfo structs. + */ + translated_joinlist = (List *) + adjust_appendrel_attrs(subroot, + (Node *) joinlist, + 1, &appinfo); + subroot->join_info_list = (List *) + adjust_appendrel_attrs(subroot, + (Node *) root->join_info_list, + 1, &appinfo); + + /* + * Sub-partitioned tables have to be processed recursively using the + * translated subroot as the parent, because AppendRelInfos link + * sub-partitions to their immediate parents, not the root partitioned + * table. + */ + if (childrel->part_scheme != NULL) + { + inheritance_make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * Add this child relation as a placeholder in the parent root's + * inh_target_child_joinrels so that inheritance_planner see same + * number of entries as inh_target_child_rels. + */ + root->inh_target_child_joinrels = + lappend(root->inh_target_child_joinrels, childrel); + + /* Also propagate this child's own children into parent's list. */ + if (subroot->inh_target_child_rels != NIL) + { + root->inh_target_child_rels = + list_concat(root->inh_target_child_rels, + subroot->inh_target_child_rels); + root->inh_target_child_joinrels = + list_concat(root->inh_target_child_joinrels, + subroot->inh_target_child_joinrels); + } + continue; + } + + /* + * Since we added the child rel directly into the join tree, we must + * modify it to be a "base" rel instead of an "other" rel, which the + * join planning code expects the relations being joined to be. + */ + childrel->reloptkind = RELOPT_BASEREL; + + Assert(subroot->join_rel_list == NIL); + Assert(subroot->join_rel_hash == NULL); + + /* Perform join planning and save the resulting RelOptInfo. */ + childjoinrel = make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * Remember this child target rel. inheritance_planner will perform + * the remaining steps of planning for each child relation separately. + * Specifically, it will call grouping_planner on every + * RelOptInfo contained in the inh_target_child_rels list, each of + * which represents the source of tuples to be modified for a given + * target child rel. + */ + root->inh_target_child_joinrels = + lappend(root->inh_target_child_joinrels, childjoinrel); +#ifdef USE_ASSERT_CHECKING + /* + * The following implements essentially the same Assert as in + * make_one_rel, our caller. + */ + all_baserels = bms_copy(root->all_baserels); + all_baserels = bms_del_member(all_baserels, + root->parse->resultRelation); + all_baserels = bms_add_member(all_baserels, + subroot->parse->resultRelation); + Assert(bms_equal(childjoinrel->relids, all_baserels)); +#endif + } +} + +/* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. * diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 6e134ae1d2..10136ef940 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -2104,12 +2104,16 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root, * * parent_rel and child_rel could be derived from appinfo, but since the * caller has already computed them, we might as well just pass them in. + * + * If 'replace' is true then the child EC members *replace* the corresponding + * parent members. */ void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, - RelOptInfo *child_rel) + RelOptInfo *child_rel, + bool replace) { ListCell *lc1; @@ -2117,14 +2121,23 @@ add_child_rel_equivalences(PlannerInfo *root, { EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc1); ListCell *lc2; + ListCell *prev; + ListCell *next; /* * If this EC contains a volatile expression, then generating child * EMs would be downright dangerous, so skip it. We rely on a - * volatile EC having only one EM. + * volatile EC having only one EM. If the caller asked to *replace* + * the original parent expression with the child one, then it's + * okay to proceed, because we wouldn't be adding another expression + * as being equivalent in that case. */ if (cur_ec->ec_has_volatile) - continue; + { + Assert(list_length(cur_ec->ec_members) == 1); + if (!replace) + continue; + } /* * No point in searching if parent rel not mentioned in eclass; but we @@ -2134,12 +2147,18 @@ add_child_rel_equivalences(PlannerInfo *root, !bms_is_subset(parent_rel->relids, cur_ec->ec_relids)) continue; - foreach(lc2, cur_ec->ec_members) + prev = NULL; + for (lc2 = list_head(cur_ec->ec_members); lc2 != NULL; lc2 = next) { EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2); + next = lnext(lc2); + if (cur_em->em_is_const) + { + prev = lc2; continue; /* ignore consts here */ + } /* Does it reference parent_rel? */ if (bms_overlap(cur_em->em_relids, parent_rel->relids)) @@ -2177,10 +2196,38 @@ add_child_rel_equivalences(PlannerInfo *root, child_rel->relids); } + /* + * Caller may have asked us to replace the parent expression + * with the child expression, so delete the parent expression + * first. + */ + if (replace) + cur_ec->ec_members = list_delete_cell(cur_ec->ec_members, + lc2, prev); + + /* + * If we're replacing, then the new member isn't really a + * a child, so em_is_child should be set to false. + */ (void) add_eq_member(cur_ec, child_expr, new_relids, new_nullable_relids, - true, cur_em->em_datatype); + !replace, cur_em->em_datatype); } + else + prev = lc2; + } + + if (replace) + { + /* + * Now fix up EC's relids set. It's OK to modify EC like this, + * because caller must have made a copy of the original EC. + * For example, see adjust_inherited_target_child_root. + */ + cur_ec->ec_relids = bms_difference(cur_ec->ec_relids, + parent_rel->relids); + cur_ec->ec_relids = bms_add_members(cur_ec->ec_relids, + child_rel->relids); } } } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 066685c3c7..8f1769b5c6 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -2022,12 +2022,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) /* * During setrefs.c, we'll need the grouping_map to fix up the cols lists * in GroupingFunc nodes. Save it for setrefs.c to use. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be grouping in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->grouping_map == NULL); root->grouping_map = grouping_map; @@ -2184,12 +2179,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) * with InitPlan output params. (We can't just do that locally in the * MinMaxAgg node, because path nodes above here may have Agg references * as well.) Save the mmaggregates list to tell setrefs.c to do that. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be aggregates in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->minmax_aggs == NIL); root->minmax_aggs = best_path->mmaggregates; diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index fc97a1bb50..96a09bb134 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -28,7 +28,11 @@ #include "optimizer/paths.h" #include "optimizer/placeholder.h" #include "optimizer/planmain.h" +#include "optimizer/prep.h" +static void add_inherited_target_child_roots(PlannerInfo *root); +static PlannerInfo *adjust_inherited_target_child_root(PlannerInfo *root, + AppendRelInfo *appinfo); /* * query_planner @@ -59,6 +63,7 @@ query_planner(PlannerInfo *root, List *tlist, Query *parse = root->parse; List *joinlist; RelOptInfo *final_rel; + Index rti; /* * If the query has an empty join tree, then it's something easy like @@ -232,14 +237,170 @@ query_planner(PlannerInfo *root, List *tlist, extract_restriction_or_clauses(root); /* + * Construct the all_baserels Relids set. + */ + root->all_baserels = NULL; + for (rti = 1; rti < root->simple_rel_array_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + Assert(brel->relid == rti); /* sanity check on array */ + + /* ignore RTEs that are "other rels" */ + if (brel->reloptkind != RELOPT_BASEREL) + continue; + + root->all_baserels = bms_add_member(root->all_baserels, brel->relid); + } + + /* + * Add child subroots needed to use during planning for individual child + * targets + */ + if (root->inherited_update) + { + root->inh_target_child_roots = (PlannerInfo **) + palloc0(root->simple_rel_array_size * + sizeof(PlannerInfo *)); + add_inherited_target_child_roots(root); + } + + /* * Ready to do the primary planning. */ final_rel = make_one_rel(root, joinlist); - /* Check that we got at least one usable path */ - if (!final_rel || !final_rel->cheapest_total_path || - final_rel->cheapest_total_path->param_info != NULL) + /* + * Check that we got at least one usable path. In the case of an + * inherited update/delete operation, no path has been created for + * the query's actual target relation yet. + */ + if (!root->inherited_update && + (!final_rel || + !final_rel->cheapest_total_path || + final_rel->cheapest_total_path->param_info != NULL)) elog(ERROR, "failed to construct the join relation"); return final_rel; } + +/* + * add_inherited_target_child_roots + * Add PlannerInfos for inheritance target children + */ +static void +add_inherited_target_child_roots(PlannerInfo *root) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; + + Assert(root->inh_target_child_roots != NULL); + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + RangeTblEntry *childRTE; + PlannerInfo *subroot; + + if (appinfo->parent_relid != resultRelation) + continue; + + childRTE = root->simple_rte_array[appinfo->child_relid]; + + /* + * Create a PlannerInfo for processing this child target relation + * with. + */ + subroot = adjust_inherited_target_child_root(root, appinfo); + + if (childRTE->inh) + add_inherited_target_child_roots(subroot); + + root->inh_target_child_roots[appinfo->child_relid] = subroot; + } +} + +/* + * add_inherit_target_child_root + * This translates query to match the child given by appinfo and + * puts it in a PlannerInfo that will be used for planning the child + * + * The child PlannerInfo reuses most of the parent PlannerInfo's fields + * unchanged, except unexpanded_tlist and processed_tlist are based on the + * child relation. + */ +static PlannerInfo * +adjust_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) +{ + PlannerInfo *subroot; + List *tlist; + ListCell *lc; + + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + + /* Translate the original query's expressions to this child. */ + subroot = makeNode(PlannerInfo); + memcpy(subroot, root, sizeof(PlannerInfo)); + + /* + * Restore the unexpanded tlist for translation, so that child's + * query contains targetList numbered (resnos) per its own + * TupleDesc, which adjust_inherited_tlist ensures. + */ + root->parse->targetList = root->unexpanded_tlist; + subroot->parse = (Query *) adjust_appendrel_attrs(root, + (Node *) root->parse, + 1, &appinfo); + + /* + * Save the original unexpanded targetlist in child subroot, just as + * we did for the parent root, so that this child's own children can use + * it. Must use copy because subroot->parse->targetList will be modified + * soon. + */ + subroot->unexpanded_tlist = list_copy(subroot->parse->targetList); + + /* + * Apply planner's expansion of targetlist, such as adding various junk + * column, filling placeholder entries for dropped columns, etc., all of + * which occurs with the child's TupleDesc. + */ + tlist = preprocess_targetlist(subroot); + subroot->processed_tlist = tlist; + + /* Add any newly added Vars to the child RelOptInfo. */ + build_base_rel_tlists(subroot, tlist); + + /* + * Adjust all_baserels to replace the original target relation with the + * child target relation. Copy it before modifying though. + */ + subroot->all_baserels = bms_copy(root->all_baserels); + subroot->all_baserels = bms_del_member(subroot->all_baserels, + root->parse->resultRelation); + subroot->all_baserels = bms_add_member(subroot->all_baserels, + subroot->parse->resultRelation); + + /* + * Child root should get its own copy of ECs, because they'll be modified + * to replace parent EC expressions by child expressions in + * add_child_rel_equivalences. + */ + subroot->eq_classes = NIL; + foreach(lc, root->eq_classes) + { + EquivalenceClass *ec = lfirst(lc); + EquivalenceClass *new_ec = makeNode(EquivalenceClass); + + memcpy(new_ec, ec, sizeof(EquivalenceClass)); + new_ec->ec_members = list_copy(ec->ec_members); + subroot->eq_classes = lappend(subroot->eq_classes, new_ec); + } + + return subroot; +} diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 98acb58304..a0bb61af41 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -37,6 +37,7 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "nodes/relation.h" #include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" @@ -128,7 +129,7 @@ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static void inheritance_planner(PlannerInfo *root); static void grouping_planner(PlannerInfo *root, bool inheritance_update, - double tuple_fraction); + RelOptInfo *planned_rel, double tuple_fraction); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, int *tleref_to_colnum_map); @@ -632,7 +633,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->grouping_map = NULL; root->minmax_aggs = NIL; root->qual_security_level = 0; - root->inhTargetKind = INHKIND_NONE; root->hasRecursion = hasRecursion; if (hasRecursion) root->wt_param_id = SS_assign_special_param(root); @@ -722,6 +722,13 @@ subquery_planner(PlannerGlobal *glob, Query *parse, expand_inherited_tables(root); /* + * Now that we have figured out "actual" inheritance situation of the + * relations, set whether the query is an inherited UPDATE/DELETE. + */ + root->inherited_update = (parse->resultRelation && + rt_fetch(parse->resultRelation, parse->rtable)->inh); + + /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -972,7 +979,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh) inheritance_planner(root); else - grouping_planner(root, false, tuple_fraction); + grouping_planner(root, false, NULL, tuple_fraction); /* * Capture the set of outer-level param IDs we have access to, for use in @@ -1148,12 +1155,19 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr) * inheritance set. * * We have to handle this case differently from cases where a source relation - * is an inheritance set. Source inheritance is expanded at the bottom of the - * plan tree (see allpaths.c), but target inheritance has to be expanded at - * the top. The reason is that for UPDATE, each target relation needs a - * different targetlist matching its own column set. Fortunately, - * the UPDATE/DELETE target can never be the nullable side of an outer join, - * so it's OK to generate the plan this way. + * is an inheritance set. That's mainly because for UPDATE, each target + * relation needs a different targetlist matching its own column set. So, we + * must modify the source scan/join path for each target relation such that it + * produces the desired target list. + * + * The source scan/join paths for individual target relations are still + * created in allpaths.c (see set_inherit_target_rel_sizes, + * set_inherit_target_rel_pathlists, and inherit_make_rel_from_joinlist). + * Here we apply grouping_planner to each child scan/join path so that it + * produces the desired targetlist. + * + * Fortunately, the UPDATE/DELETE target can never be the nullable side of an + * outer join, so it's OK to generate the plan this way. * * Returns nothing; the useful output is in the Paths we attach to * the (UPPERREL_FINAL, NULL) upperrel stored in *root. @@ -1166,14 +1180,8 @@ inheritance_planner(PlannerInfo *root) { Query *parse = root->parse; int top_parentRTindex = parse->resultRelation; - Bitmapset *subqueryRTindexes; - Bitmapset *modifiableARIindexes; int nominalRelation = -1; Index rootRelation = 0; - List *final_rtable = NIL; - int save_rel_array_size = 0; - RelOptInfo **save_rel_array = NULL; - AppendRelInfo **save_append_rel_array = NULL; List *subpaths = NIL; List *subroots = NIL; List *resultRelations = NIL; @@ -1181,70 +1189,58 @@ inheritance_planner(PlannerInfo *root) List *returningLists = NIL; List *rowMarks; RelOptInfo *final_rel; - ListCell *lc; - Index rti; + ListCell *lc1, + *lc2; RangeTblEntry *parent_rte; - PlannerInfo *parent_root; - Query *parent_parse; - Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); - PlannerInfo **parent_roots = NULL; + List *tlist; + standard_qp_extra qp_extra; + RelOptInfo *planned_rel; + /* Inheritance is never used for insert. */ Assert(parse->commandType != CMD_INSERT); /* - * We generate a modified instance of the original Query for each target - * relation, plan that, and put all the plans into a list that will be - * controlled by a single ModifyTable node. All the instances share the - * same rangetable, but each instance must have its own set of subquery - * RTEs within the finished rangetable because (1) they are likely to get - * scribbled on during planning, and (2) it's not inconceivable that - * subqueries could get planned differently in different cases. We need - * not create duplicate copies of other RTE kinds, in particular not the - * target relations, because they don't have either of those issues. Not - * having to duplicate the target relations is important because doing so - * (1) would result in a rangetable of length O(N^2) for N targets, with - * at least O(N^3) work expended here; and (2) would greatly complicate - * management of the rowMarks list. + * Generate the access paths for all relations mentioned in the query, + * including the target inheritance set. When doing the join planning, + * references in the join tree to the original target relation that's the + * root parent of the inheritance tree are replaced by each of its + * inheritance children and the resulting joinrel RelOptInfo's are + * added to root->inh_target_child_rels. * - * To begin with, generate a bitmapset of the relids of the subquery RTEs. + * Final planning steps (grouping_planner) are applied to the best path + * of each of those child joinrels using a modified instance of the + * original query for a given child target rel. All the paths so + * generated are put into a list that will be controlled by a single + * ModifyTable node. All the instances share the same rangetable. */ - subqueryRTindexes = NULL; - rti = 1; - foreach(lc, parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - if (rte->rtekind == RTE_SUBQUERY) - subqueryRTindexes = bms_add_member(subqueryRTindexes, rti); - rti++; - } /* - * Next, we want to identify which AppendRelInfo items contain references - * to any of the aforesaid subquery RTEs. These items will need to be - * copied and modified to adjust their subquery references; whereas the - * other ones need not be touched. It's worth being tense over this - * because we can usually avoid processing most of the AppendRelInfo - * items, thereby saving O(N^2) space and time when the target is a large - * inheritance tree. We can identify AppendRelInfo items by their - * child_relid, since that should be unique within the list. + * Save the unexpanded version of the query's targetlist to be used below + * for passing to grouping_planner for each child target relation. */ - modifiableARIindexes = NULL; - if (subqueryRTindexes != NULL) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); + root->unexpanded_tlist = list_copy(root->parse->targetList); - if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) || - bms_is_member(appinfo->child_relid, subqueryRTindexes) || - bms_overlap(pull_varnos((Node *) appinfo->translated_vars), - subqueryRTindexes)) - modifiableARIindexes = bms_add_member(modifiableARIindexes, - appinfo->child_relid); - } + /* Do the scan/join planning. */ + tlist = preprocess_targetlist(root); + root->processed_tlist = tlist; + qp_extra.tlist = tlist; + qp_extra.activeWindows = qp_extra.groupClause = NIL; + planned_rel = query_planner(root, tlist, standard_qp_callback, &qp_extra); + + /* + * If it turned out during query planning that all the children are dummy + * (pruned or excluded by constraints), no need to do the steps below. + * Let grouping_planner finish up the final path. + */ + if (IS_DUMMY_REL(planned_rel)) + { + grouping_planner(root, false, planned_rel, 0.0); + return; } + Assert(planned_rel->relid == top_parentRTindex); + Assert(planned_rel->reloptkind == RELOPT_BASEREL); + /* * If the parent RTE is a partitioned table, we should use that as the * nominal target relation, because the RTEs added for partitioned tables @@ -1252,7 +1248,7 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); + parent_rte = planner_rt_fetch(top_parentRTindex, root); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1260,95 +1256,42 @@ inheritance_planner(PlannerInfo *root) } /* - * The PlannerInfo for each child is obtained by translating the relevant - * members of the PlannerInfo for its immediate parent, which we find - * using the parent_relid in its AppendRelInfo. We save the PlannerInfo - * for each parent in an array indexed by relid for fast retrieval. Since - * the maximum number of parents is limited by the number of RTEs in the - * query, we use that number to allocate the array. An extra entry is - * needed since relids start from 1. + * Get on with finalizing the path for each child target relation by + * calling grouping_planner on its joinrel. Note that we're restoring + * the query's targetlist to the original one for grouping_planner's + * targetlist expansion steps to perform the expansion with individual + * child descriptors. */ - parent_roots = (PlannerInfo **) palloc0((list_length(parse->rtable) + 1) * - sizeof(PlannerInfo *)); - parent_roots[top_parentRTindex] = root; - - /* - * And now we can get on with generating a plan for each child table. - */ - foreach(lc, root->append_rel_list) + forboth(lc1, root->inh_target_child_rels, + lc2, root->inh_target_child_joinrels) { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - PlannerInfo *subroot; + Index childRTindex = lfirst_int(lc1); + RelOptInfo *childrel = lfirst(lc2); + PlannerInfo *subroot = root->inh_target_child_roots[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; RangeTblEntry *child_rte; RelOptInfo *sub_final_rel; Path *subpath; - /* append_rel_list contains all append rels; ignore others */ - if (!bms_is_member(appinfo->parent_relid, parent_relids)) - continue; - - /* - * expand_inherited_rtentry() always processes a parent before any of - * that parent's children, so the parent_root for this relation should - * already be available. - */ - parent_root = parent_roots[appinfo->parent_relid]; - Assert(parent_root != NULL); - parent_parse = parent_root->parse; - - /* - * We need a working copy of the PlannerInfo so that we can control - * propagation of information back to the main copy. - */ - subroot = makeNode(PlannerInfo); - memcpy(subroot, parent_root, sizeof(PlannerInfo)); - - /* - * Generate modified query with this rel as target. We first apply - * adjust_appendrel_attrs, which copies the Query and changes - * references to the parent RTE to refer to the current child RTE, - * then fool around with subquery RTEs. - */ - subroot->parse = (Query *) - adjust_appendrel_attrs(parent_root, - (Node *) parent_parse, - 1, &appinfo); + Assert(subroot != NULL); + Assert(subroot->parse->resultRelation == childRTindex); /* * If there are securityQuals attached to the parent, move them to the * child rel (they've already been transformed properly for that). */ - parent_rte = rt_fetch(appinfo->parent_relid, subroot->parse->rtable); - child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable); + parent_rte = planner_rt_fetch(appinfo->parent_relid, subroot); + child_rte = planner_rt_fetch(appinfo->child_relid, subroot); child_rte->securityQuals = parent_rte->securityQuals; parent_rte->securityQuals = NIL; /* - * Mark whether we're planning a query to a partitioned table or an - * inheritance parent. + * Ignore a partitioned child. Instead, the paths of its children will + * be added to subpaths. */ - subroot->inhTargetKind = - (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED; - - /* - * If this child is further partitioned, remember it as a parent. - * Since a partitioned table does not have any data, we don't need to - * create a plan for it, and we can stop processing it here. We do, - * however, need to remember its modified PlannerInfo for use when - * processing its children, since we'll update their varnos based on - * the delta from immediate parent to child, not from top to child. - * - * Note: a very non-obvious point is that we have not yet added - * duplicate subquery RTEs to the subroot's rtable. We mustn't, - * because then its children would have two sets of duplicates, - * confusing matters. - */ - if (child_rte->inh) + if (childrel->part_scheme) { - Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE); - parent_relids = bms_add_member(parent_relids, appinfo->child_relid); - parent_roots[appinfo->child_relid] = subroot; - + Assert(child_rte->inh); continue; } @@ -1376,111 +1319,8 @@ inheritance_planner(PlannerInfo *root) if (nominalRelation < 0) nominalRelation = appinfo->child_relid; - /* - * The rowMarks list might contain references to subquery RTEs, so - * make a copy that we can apply ChangeVarNodes to. (Fortunately, the - * executor doesn't need to see the modified copies --- we can just - * pass it the original rowMarks list.) - */ - subroot->rowMarks = copyObject(parent_root->rowMarks); - - /* - * The append_rel_list likewise might contain references to subquery - * RTEs (if any subqueries were flattenable UNION ALLs). So prepare - * to apply ChangeVarNodes to that, too. As explained above, we only - * want to copy items that actually contain such references; the rest - * can just get linked into the subroot's append_rel_list. - * - * If we know there are no such references, we can just use the outer - * append_rel_list unmodified. - */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - subroot->append_rel_list = NIL; - foreach(lc2, parent_root->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, modifiableARIindexes)) - appinfo2 = copyObject(appinfo2); - - subroot->append_rel_list = lappend(subroot->append_rel_list, - appinfo2); - } - } - - /* - * Add placeholders to the child Query's rangetable list to fill the - * RT indexes already reserved for subqueries in previous children. - * These won't be referenced, so there's no need to make them very - * valid-looking. - */ - while (list_length(subroot->parse->rtable) < list_length(final_rtable)) - subroot->parse->rtable = lappend(subroot->parse->rtable, - makeNode(RangeTblEntry)); - - /* - * If this isn't the first child Query, generate duplicates of all - * subquery RTEs, and adjust Var numbering to reference the - * duplicates. To simplify the loop logic, we scan the original rtable - * not the copy just made by adjust_appendrel_attrs; that should be OK - * since subquery RTEs couldn't contain any references to the target - * rel. - */ - if (final_rtable != NIL && subqueryRTindexes != NULL) - { - ListCell *lr; - - rti = 1; - foreach(lr, parent_parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lr); - - if (bms_is_member(rti, subqueryRTindexes)) - { - Index newrti; - - /* - * The RTE can't contain any references to its own RT - * index, except in its securityQuals, so we can save a - * few cycles by applying ChangeVarNodes to the rest of - * the rangetable before we append the RTE to it. - */ - newrti = list_length(subroot->parse->rtable) + 1; - ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); - ChangeVarNodes((Node *) subroot->rowMarks, rti, newrti, 0); - /* Skip processing unchanging parts of append_rel_list */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - foreach(lc2, subroot->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, - modifiableARIindexes)) - ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); - } - } - rte = copyObject(rte); - ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); - subroot->parse->rtable = lappend(subroot->parse->rtable, - rte); - } - rti++; - } - } - - /* There shouldn't be any OJ info to translate, as yet */ - Assert(subroot->join_info_list == NIL); - /* and we haven't created PlaceHolderInfos, either */ - Assert(subroot->placeholder_list == NIL); - - /* Generate Path(s) for accessing this result relation */ - grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ ); + /* Apply the top-level targetlist to childrel's path. */ + grouping_planner(subroot, true, childrel, 0.0); /* * Select cheapest path in case there's more than one. We always run @@ -1492,45 +1332,10 @@ inheritance_planner(PlannerInfo *root) subpath = sub_final_rel->cheapest_total_path; /* - * If this child rel was excluded by constraint exclusion, exclude it - * from the result plan. + * child rel cannot be empty, or inheritance_make_rel_from_joinlist + * wouldn't have put it in the list to begin with. */ - if (IS_DUMMY_PATH(subpath)) - continue; - - /* - * If this is the first non-excluded child, its post-planning rtable - * becomes the initial contents of final_rtable; otherwise, append - * just its modified subquery RTEs to final_rtable. - */ - if (final_rtable == NIL) - final_rtable = subroot->parse->rtable; - else - final_rtable = list_concat(final_rtable, - list_copy_tail(subroot->parse->rtable, - list_length(final_rtable))); - - /* - * We need to collect all the RelOptInfos from all child plans into - * the main PlannerInfo, since setrefs.c will need them. We use the - * last child's simple_rel_array (previous ones are too short), so we - * have to propagate forward the RelOptInfos that were already built - * in previous children. - */ - Assert(subroot->simple_rel_array_size >= save_rel_array_size); - for (rti = 1; rti < save_rel_array_size; rti++) - { - RelOptInfo *brel = save_rel_array[rti]; - - if (brel) - subroot->simple_rel_array[rti] = brel; - } - save_rel_array_size = subroot->simple_rel_array_size; - save_rel_array = subroot->simple_rel_array; - save_append_rel_array = subroot->append_rel_array; - - /* Make sure any initplans from this rel get into the outer list */ - root->init_plans = subroot->init_plans; + Assert(!IS_DUMMY_PATH(subpath)); /* Build list of sub-paths */ subpaths = lappend(subpaths, subpath); @@ -1562,36 +1367,6 @@ inheritance_planner(PlannerInfo *root) */ /* - * If we managed to exclude every child rel, return a dummy plan; it - * doesn't even need a ModifyTable node. - */ - if (subpaths == NIL) - { - set_dummy_rel_pathlist(final_rel); - return; - } - - /* - * Put back the final adjusted rtable into the master copy of the Query. - * (We mustn't do this if we found no non-excluded children.) - */ - parse->rtable = final_rtable; - root->simple_rel_array_size = save_rel_array_size; - root->simple_rel_array = save_rel_array; - root->append_rel_array = save_append_rel_array; - - /* Must reconstruct master's simple_rte_array, too */ - root->simple_rte_array = (RangeTblEntry **) - palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *)); - rti = 1; - foreach(lc, final_rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - root->simple_rte_array[rti++] = rte; - } - - /* * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will * have dealt with fetching non-locked marked rows, else we need to have * ModifyTable do that. @@ -1631,6 +1406,12 @@ inheritance_planner(PlannerInfo *root) * (inheritance_planner will create a single ModifyTable node covering all the * target tables.) * + * If non-NULL, planned_rel is a RelOptInfo containing paths for the query's + * top-level joinrel, which the caller produced by itself. In that case, this + * function only needs to adjust the targetlist of its cheapest_total_path. + * The only caller that may pass such a RelOptInfo currently is + * inheritance_planner. + * * tuple_fraction is the fraction of tuples we expect will be retrieved. * tuple_fraction is interpreted as follows: * 0: expect all tuples to be retrieved (normal case) @@ -1649,6 +1430,7 @@ inheritance_planner(PlannerInfo *root) */ static void grouping_planner(PlannerInfo *root, bool inheritance_update, + RelOptInfo *planned_rel, double tuple_fraction) { Query *parse = root->parse; @@ -1661,7 +1443,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, List *final_targets; List *final_targets_contain_srfs; bool final_target_parallel_safe; - RelOptInfo *current_rel; + RelOptInfo *current_rel = planned_rel; RelOptInfo *final_rel; ListCell *lc; @@ -1701,6 +1483,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * special work for recursive unions is the responsibility of * plan_set_operations. */ + Assert(current_rel == NULL); current_rel = plan_set_operations(root); /* @@ -1790,17 +1573,26 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, parse->groupClause = preprocess_groupclause(root, NIL); } - /* Preprocess targetlist */ - tlist = preprocess_targetlist(root); - /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation so - * that we can transfer its decoration (resnames etc) to the topmost - * tlist of the finished Plan. + * Preprocess targetlist, if needed. If the caller has already done + * query planning, root->processed_tlist already contains the desired + * targetlist. */ - root->processed_tlist = tlist; + if (planned_rel == NULL) + { + tlist = preprocess_targetlist(root); + + /* + * We are now done hacking up the query's targetlist. Most of the + * remaining planning work will be done with the PathTarget + * representation of tlists, but save aside the full representation + * so that we can transfer its decoration (resnames etc) to the + * topmost tlist of the finished Plan. + */ + root->processed_tlist = tlist; + } + else + tlist = root->processed_tlist; /* * Collect statistics about aggregates for estimating costs, and mark @@ -1880,8 +1672,9 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * We also generate (in standard_qp_callback) pathkey representations * of the query's sort clause, distinct clause, etc. */ - current_rel = query_planner(root, tlist, - standard_qp_callback, &qp_extra); + if (current_rel == NULL) + current_rel = query_planner(root, tlist, + standard_qp_callback, &qp_extra); /* * Convert the query's result tlist into PathTarget format. diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 77dbf4eba3..87adfa02b1 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -917,7 +917,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->grouping_map = NULL; subroot->minmax_aggs = NIL; subroot->qual_security_level = 0; - subroot->inhTargetKind = INHKIND_NONE; subroot->hasRecursion = false; subroot->wt_param_id = -1; subroot->non_recursive_path = NULL; diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index 37fbc3a117..0b9ca39a51 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -394,8 +394,39 @@ adjust_appendrel_attrs_mutator(Node *node, context->appinfos); return (Node *) phv; } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 48ffc5f254..6009de309e 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1264,36 +1264,6 @@ get_relation_constraints(PlannerInfo *root, } } - /* - * Append partition predicates, if any. - * - * For selects, partition pruning uses the parent table's partition bound - * descriptor, instead of constraint exclusion which is driven by the - * individual partition's partition constraint. - */ - if (enable_partition_pruning && root->parse->commandType != CMD_SELECT) - { - List *pcqual = RelationGetPartitionQual(relation); - - if (pcqual) - { - /* - * Run the partition quals through const-simplification similar to - * check constraints. We skip canonicalize_qual, though, because - * partition quals should be in canonical form already; also, - * since the qual is in implicit-AND format, we'd have to - * explicitly convert it to explicit-AND format and back again. - */ - pcqual = (List *) eval_const_expressions(root, (Node *) pcqual); - - /* Fix Vars to have the desired varno */ - if (varno != 1) - ChangeVarNodes((Node *) pcqual, 1, varno, 0); - - result = list_concat(result, pcqual); - } - } - heap_close(relation, NoLock); return result; @@ -1417,40 +1387,15 @@ relation_excluded_by_constraints(PlannerInfo *root, /* * Skip further tests, depending on constraint_exclusion. */ - switch (constraint_exclusion) - { - case CONSTRAINT_EXCLUSION_OFF: - - /* - * Don't prune if feature turned off -- except if the relation is - * a partition. While partprune.c-style partition pruning is not - * yet in use for all cases (update/delete is not handled), it - * would be a UI horror to use different user-visible controls - * depending on such a volatile implementation detail. Therefore, - * for partitioned tables we use enable_partition_pruning to - * control this behavior. - */ - if (root->inhTargetKind == INHKIND_PARTITIONED) - break; - return false; - - case CONSTRAINT_EXCLUSION_PARTITION: - - /* - * When constraint_exclusion is set to 'partition' we only handle - * OTHER_MEMBER_RELs, or BASERELs in cases where the result target - * is an inheritance parent or a partitioned table. - */ - if ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) && - !(rel->reloptkind == RELOPT_BASEREL && - root->inhTargetKind != INHKIND_NONE && - rel->relid == root->parse->resultRelation)) - return false; - break; - - case CONSTRAINT_EXCLUSION_ON: - break; /* always try to exclude */ - } + if (constraint_exclusion == CONSTRAINT_EXCLUSION_OFF) + return false; + /* + * When constraint_exclusion is set to 'partition' we only handle + * OTHER_MEMBER_RELs. + */ + else if (constraint_exclusion == CONSTRAINT_EXCLUSION_PARTITION && + rel->reloptkind != RELOPT_OTHER_MEMBER_REL) + return false; /* * Check for self-contradictory restriction clauses. We dare not make diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 3430061361..beb2e323a8 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -319,9 +319,6 @@ typedef struct PlannerInfo Index qual_security_level; /* minimum security_level for quals */ /* Note: qual_security_level is zero if there are no securityQuals */ - InheritanceKind inhTargetKind; /* indicates if the target relation is an - * inheritance child or partition or a - * partitioned table */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */ bool hasDeletedRTEs; /* true if any RTE was deleted from jointree */ @@ -343,6 +340,40 @@ typedef struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* + * The following fields are set during query planning portion of an + * inherited UPDATE/DEELETE operation. + */ + bool inherited_update; /* UPDATE/DELETE on inheritance parent? */ + + /* + * This stores the original version of the query's targetlist that's + * not modified by the planner. + */ + List *unexpanded_tlist; + + /* + * Array containing simple_rel_array_size elements, indexed by rangetable + * index (entry 0 is wasted like simple_rel_array). Only elements + * corresponding to individual inheritance child target relations are + * non-NULL. Content of each PlannerInfo is same as the parent + * PlannerInfo, except for the parse tree which is a translated copy of + * the parent's parse tree. + */ + struct PlannerInfo **inh_target_child_roots; + + /* List of RT indexes of child RT indexes. */ + List *inh_target_child_rels; + + /* + * RelOptInfos corresponding to each child target rel. For leaf children, + * it's the RelOptInfo representing the output of make_rel_from_joinlist() + * called with the parent rel in the original join tree replaced by a + * given leaf child. For non-leaf children, it's the baserel RelOptInfo + * itself, left as a placeholder. + */ + List *inh_target_child_joinrels; } PlannerInfo; diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 666217c189..f524ab6beb 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -161,7 +161,8 @@ extern EquivalenceClass *match_eclasses_to_foreign_key_col(PlannerInfo *root, extern void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, - RelOptInfo *child_rel); + RelOptInfo *child_rel, + bool replace); extern List *generate_implied_equalities_for_column(PlannerInfo *root, RelOptInfo *rel, ec_matches_callback_type callback, diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index c55de5d476..c9f02c1fb0 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1780,7 +1780,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_1 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_1 -> Nested Loop Semi Join @@ -1788,7 +1788,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_2 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_2 (28 rows) -- 2.11.0
v13-0003-Store-inheritance-root-parent-index-in-otherrel-.patch (text/plain, 3.3K) download From 2dc0d62b9475841805caa4dbc38cb62fc85251a5 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 26 Oct 2018 16:45:59 +0900 Subject: [PATCH v13 3/6] Store inheritance root parent index in otherrel's RelOptInfo Although it's set by build_simple_rel, it's not being used by any code yet. --- src/backend/nodes/outfuncs.c | 1 + src/backend/optimizer/util/relnode.c | 15 +++++++++++++-- src/include/nodes/relation.h | 9 +++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 158da68925..67697b8884 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2272,6 +2272,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_BOOL_FIELD(consider_partitionwise_join); WRITE_BITMAPSET_FIELD(top_parent_relids); WRITE_NODE_FIELD(partitioned_child_rels); + WRITE_UINT_FIELD(inh_root_parent); } static void diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index fe83ec4519..45f9950935 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -195,6 +195,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->joininfo = NIL; rel->has_eclass_joins = false; rel->consider_partitionwise_join = false; /* might get changed later */ + rel->top_parent_relids = NULL; /* might be changed later */ rel->part_scheme = NULL; rel->nparts = 0; rel->boundinfo = NULL; @@ -203,6 +204,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->partexprs = NULL; rel->nullable_partexprs = NULL; rel->partitioned_child_rels = NIL; + rel->inh_root_parent = 0; /* might be changed later */ /* * Pass top parent's relids down the inheritance hierarchy. If the parent @@ -216,9 +218,18 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->top_parent_relids = parent->top_parent_relids; else rel->top_parent_relids = bms_copy(parent->relids); + + /* + * For inheritance child relations, we also set inh_root_parent. + * Note that 'parent' might itself be a child (a sub-partitioned + * partition), in which case we simply use its value of + * inh_root_parent. + */ + if (parent->rtekind == RTE_RELATION) + rel->inh_root_parent = parent->inh_root_parent > 0 ? + parent->inh_root_parent : + parent->relid; } - else - rel->top_parent_relids = NULL; /* Check type of rtable entry */ switch (rte->rtekind) diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index beb2e323a8..db439bd904 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -734,6 +734,15 @@ typedef struct RelOptInfo List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ + + /* + * For inheritance children, this is the RT index of inheritance table + * mentioned in the query from which this relation originated. + * top_parent_relids cannot be used for this, because if the inheritance + * root table is itself under UNION ALL, top_parent_relids contains the + * RT index of UNION ALL parent subquery. + */ + Index inh_root_parent; } RelOptInfo; /* -- 2.11.0
v13-0004-Lazy-creation-of-RTEs-for-inheritance-children.patch (text/plain, 93.6K) download From 8b1f8e5d6027bae1d560ba123d85002494681d70 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 18:14:13 +0900 Subject: [PATCH v13 4/6] Lazy creation of RTEs for inheritance children Currently, expand_inherited_tables called from subquery_planner opens all inheritance child tables and adds them to the query's range table and PlannerInfo in the form of AppendRelInfo's initially and later RelOptInfo's. For partitioning, that's pretty wasteful, because it's possible to determine only the partitions that will need to be scanned using partition pruning, which doesn't require opening the partitions themselves. However, the point at which the above processing happens is too early to perform partition pruning. This commit rearranges things within the planner so that inheritance child relations can be added to the Query and PlannerInfo during make_one_rel, just before set_base_rel_sizes is called. Although the late initialization approach only benefits the partitioning case, it seems undesirable to do it only for partitioned tables, because it means adding code to handle partitions specially at various places within the planner. So, *all* inheritance parent tables are expanded in make_one_rel. All unpruned partitions are added to Query in the form of their RangeTblEntry's being added to the range table and to PlannerInfo in the form of an AppendRelInfo and a RelOptInfo for each. For partitioning, although we don't create a RangeTblEntry and RelOptInfo for pruned partitions at make_one_rel time, partitionwise join code relies on the fact that even though partitions may have been pruned, they'd still own a RelOptInfo to handle the outer join case where the pruned partition appears on the nullable side of join. Partitionwise join code deals with that by allocating dummy RelOptInfos for pruned partitions that are based mostly on their parent's properties. There are some regression test diffs: 1. Caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child, as seen in the partition_aggregate.out test output. 2. Those in postgres_fdw.out are caused by the fact that junk columns required for row marking are added to reltarget->exprs later than user columns, because the row marking junk columns arent added until after the inheritance is expanded which as of this commit is later than it used to be. --- contrib/postgres_fdw/expected/postgres_fdw.out | 32 +- src/backend/optimizer/path/allpaths.c | 357 +--------- src/backend/optimizer/path/joinrels.c | 61 ++ src/backend/optimizer/plan/initsplan.c | 58 -- src/backend/optimizer/plan/planmain.c | 12 +- src/backend/optimizer/plan/planner.c | 77 ++- src/backend/optimizer/plan/setrefs.c | 6 + src/backend/optimizer/prep/preptlist.c | 15 +- src/backend/optimizer/prep/prepunion.c | 3 + src/backend/optimizer/util/appendinfo.c | 45 +- src/backend/optimizer/util/inherit.c | 808 +++++++++++++++------- src/backend/optimizer/util/plancat.c | 47 +- src/backend/optimizer/util/relnode.c | 84 +-- src/backend/partitioning/partprune.c | 43 +- src/include/nodes/relation.h | 9 + src/include/optimizer/appendinfo.h | 7 +- src/include/optimizer/pathnode.h | 1 + src/include/optimizer/plancat.h | 4 +- src/include/optimizer/prep.h | 3 +- src/include/partitioning/partprune.h | 2 +- src/test/regress/expected/partition_aggregate.out | 4 +- 21 files changed, 901 insertions(+), 777 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index bb92d9d37a..33a33606ef 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7072,15 +7072,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7110,15 +7110,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7147,15 +7147,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid @@ -7165,15 +7165,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (39 rows) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 35dba9c7ce..e3b93cb108 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -45,7 +45,6 @@ #include "optimizer/var.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -383,6 +382,14 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, else if (rte->inh) { /* + * expand_inherited_tables may have proved that the relation is empty. + * For example, if it's a partitioned table with 0 partitions or all + * of its partitions are pruned. In that case nothing to do here. + */ + if (IS_DUMMY_REL(rel)) + return; + + /* * If it's a target relation, set the sizes of children instead. * Otherwise, we'll append the outputs of children, so process it as * an "append relation". @@ -951,38 +958,18 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, int parentRTindex = rti; bool has_live_children; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); Assert(IS_SIMPLE_REL(rel)); - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - foreach(l, root->append_rel_list) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; PlannerInfo *subroot; ListCell *lc; List *translated_exprs, @@ -997,139 +984,18 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, subroot = root->inh_target_child_roots[childRTindex]; /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals. */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); + if (IS_DUMMY_REL(childrel)) continue; - } - - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } if (relation_excluded_by_constraints(root, childrel, childRTE)) { @@ -1297,8 +1163,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -1306,32 +1170,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Assert(IS_SIMPLE_REL(rel)); /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, we will bubble up - * the indexes of partitioned relations that appear down in the tree, so - * that when we've created Paths for all the children, the root - * partitioned table's list will contain all such indexes. - */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - rel->partitioned_child_rels = list_make1_int(rti); - - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - - /* * If this is a partitioned baserel, set the consider_partitionwise_join * flag; currently, we only consider partitionwise joins with the baserel * if its targetlist doesn't contain a whole-row Var. @@ -1368,12 +1206,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; ListCell *parentvars; ListCell *childvars; - ListCell *lc; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1381,18 +1215,34 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); /* - * Copy/Modify targetlist. Even if this child is deemed empty, we need - * its targetlist in case it falls on nullable side in a child-join - * because of partitionwise join. + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals. + */ + if (IS_DUMMY_REL(childrel)) + continue; + + if (relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* + * This child need not be scanned, so we can omit it from the + * appendrel. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Copy/Modify targetlist. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -1422,144 +1272,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, false); childrel->has_eclass_joins = rel->has_eclass_joins; - /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* - * This child need not be scanned, so we can omit it from the - * appendrel. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - /* CE failed, so finish copying/modifying join quals. */ childrel->joininfo = (List *) adjust_appendrel_attrs(root, @@ -3035,6 +2747,7 @@ inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) continue; childrel = find_base_rel(root, appinfo->child_relid); + Assert(childrel != NULL); /* Ignore excluded/pruned children. */ if (IS_DUMMY_REL(childrel)) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index db18feccfe..411cdbd84f 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -20,6 +20,7 @@ #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" +#include "optimizer/tlist.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -49,6 +50,9 @@ static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, static SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, Relids left_relids, Relids right_relids); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + int partidx); /* @@ -1379,6 +1383,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (IS_SIMPLE_REL(rel1) && child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts); + if (IS_SIMPLE_REL(rel1) && child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts); + /* We should never try to join two overlapping sets of rels. */ Assert(!bms_overlap(child_rel1->relids, child_rel2->relids)); child_joinrelids = bms_union(child_rel1->relids, child_rel2->relids); @@ -1627,3 +1636,55 @@ build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, return sjinfo; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +static RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx) +{ + RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + AppendRelInfo *appinfo = make_append_rel_info(parent, parentrte, + parent->tupdesc, + parentrte->relid, + parent->reltype, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index a66374094f..c628f3d9e7 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -616,64 +616,6 @@ create_lateral_join_info(PlannerInfo *root) bms_add_member(brel2->lateral_referencers, rti); } } - - /* - * Lastly, propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - */ - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - RangeTblEntry *brte = root->simple_rte_array[rti]; - - /* - * Skip empty slots. Also skip non-simple relations i.e. dead - * relations. - */ - if (brel == NULL || !IS_SIMPLE_REL(brel)) - continue; - - /* - * In the case of table inheritance, the parent RTE is directly linked - * to every child table via an AppendRelInfo. In the case of table - * partitioning, the inheritance hierarchy is expanded one level at a - * time rather than flattened. Therefore, an other member rel that is - * a partitioned table may have children of its own, and must - * therefore be marked with the appropriate lateral info so that those - * children eventually get marked also. - */ - Assert(brte); - if (brel->reloptkind == RELOPT_OTHER_MEMBER_REL && - (brte->rtekind != RTE_RELATION || - brte->relkind != RELKIND_PARTITIONED_TABLE)) - continue; - - if (brte->inh) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); - RelOptInfo *childrel; - - if (appinfo->parent_relid != rti) - continue; - childrel = root->simple_rel_array[appinfo->child_relid]; - Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - Assert(childrel->direct_lateral_relids == NULL); - childrel->direct_lateral_relids = brel->direct_lateral_relids; - Assert(childrel->lateral_relids == NULL); - childrel->lateral_relids = brel->lateral_relids; - Assert(childrel->lateral_referencers == NULL); - childrel->lateral_referencers = brel->lateral_referencers; - } - } - } } diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 96a09bb134..2021162fdb 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -258,6 +258,16 @@ query_planner(PlannerInfo *root, List *tlist, } /* + * Expand RT entries that represent inherited or partitioned tables. + * This will perform partition pruning on partitioned tables in the + * original range table and also recursively on any child partitioned + * tables that were added by the expansion of the original parent(s). + * As new entries are added to the range table, various arrays in the + * PlannerInfo will be expanded accordingly. + */ + expand_inherited_tables(root); + + /* * Add child subroots needed to use during planning for individual child * targets */ @@ -370,7 +380,7 @@ adjust_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) * column, filling placeholder entries for dropped columns, etc., all of * which occurs with the child's TupleDesc. */ - tlist = preprocess_targetlist(subroot); + tlist = preprocess_targetlist(subroot, true); subroot->processed_tlist = tlist; /* Add any newly added Vars to the child RelOptInfo. */ diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index a0bb61af41..6ea2fca611 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -23,6 +23,7 @@ #include "access/sysattr.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -701,27 +702,18 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } /* - * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). - */ - preprocess_rowmarks(root); - - /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain base relations not joins, so it's OK (and marginally more - * efficient) to do it after checking for join RTEs. We must do it after - * pulling up subqueries, else we'd fail to handle inherited tables in - * subqueries. - */ - expand_inherited_tables(root); - - /* * Now that we have figured out "actual" inheritance situation of the * relations, set whether the query is an inherited UPDATE/DELETE. */ @@ -729,6 +721,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh); /* + * Preprocess RowMark information. We need to do this after subquery + * pullup (so that all non-inherited RTEs are present). + */ + preprocess_rowmarks(root); + + /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -1220,8 +1218,11 @@ inheritance_planner(PlannerInfo *root) */ root->unexpanded_tlist = list_copy(root->parse->targetList); - /* Do the scan/join planning. */ - tlist = preprocess_targetlist(root); + /* + * Do the scan/join planning. We haven't expanded inheritance yet, so + * pass false. + */ + tlist = preprocess_targetlist(root, false); root->processed_tlist = tlist; qp_extra.tlist = tlist; qp_extra.activeWindows = qp_extra.groupClause = NIL; @@ -1230,9 +1231,11 @@ inheritance_planner(PlannerInfo *root) /* * If it turned out during query planning that all the children are dummy * (pruned or excluded by constraints), no need to do the steps below. - * Let grouping_planner finish up the final path. + * For regular inheritance case, it's possible that we only need to modify + * the parent table. Let grouping_planner finish up the final path. */ - if (IS_DUMMY_REL(planned_rel)) + parent_rte = planner_rt_fetch(top_parentRTindex, root); + if (!parent_rte->inh || IS_DUMMY_REL(planned_rel)) { grouping_planner(root, false, planned_rel, 0.0); return; @@ -1248,7 +1251,6 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = planner_rt_fetch(top_parentRTindex, root); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1580,14 +1582,19 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, */ if (planned_rel == NULL) { - tlist = preprocess_targetlist(root); + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root, false); /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation - * so that we can transfer its decoration (resnames etc) to the - * topmost tlist of the finished Plan. + * We are now mostly done hacking up the query's targetlist. More + * columns might be added during query_planner after inheritance + * expansion in make_one_rel, because some children may require + * different column than the parent for row mark handling; see + * add_rowmark_junk_columns(). Most of the remaining planning + * work will be done with the PathTarget representation of tlists, + * but save aside the full representation so that we can transfer + * its decoration (resnames etc) to the topmost tlist of the + * finished Plan. */ root->processed_tlist = tlist; } @@ -2363,7 +2370,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2388,7 +2395,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } @@ -6825,6 +6832,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -6926,6 +6937,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 5d363edab8..4dcdede6b6 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -325,6 +325,12 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) UPPERREL_FINAL, NULL))) add_rtes_to_flat_rtable(rel->subroot, true); } + /* + * A NULL rel also means an unplanned subquery rte, so apply + * flatten_unplanned_rtes. + */ + else + flatten_unplanned_rtes(glob, rte); } rti++; } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index f8bc2dd257..a55949c305 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -65,9 +65,13 @@ static List *expand_targetlist(List *tlist, int command_type, * * As a side effect, if there's an ON CONFLICT UPDATE clause, its targetlist * is also preprocessed (and updated in-place). + * + * inheritance_expanded specifies whether inheritance tables are expanded + * as of calling this function. Caller must specify it because that affects + * which row marking related junk columns get added to the targetlist. */ List * -preprocess_targetlist(PlannerInfo *root) +preprocess_targetlist(PlannerInfo *root, bool inheritance_expanded) { Query *parse = root->parse; int result_relation = parse->resultRelation; @@ -134,6 +138,15 @@ preprocess_targetlist(PlannerInfo *root) if (rc->rti != rc->prti) continue; + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which will change the parent row + * mark's allMarkTypes fields. + */ + if (rc->isParent && !inheritance_expanded) + continue; + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) { /* Need to fetch TID */ diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 05388355fd..0fb01e2145 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -32,13 +32,16 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" +#include "optimizer/var.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" #include "utils/lsyscache.h" diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index 0b9ca39a51..1500ea22b9 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -31,10 +31,10 @@ typedef struct AppendRelInfo **appinfos; } adjust_appendrel_attrs_context; -static void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, adjust_appendrel_attrs_context *context); static List *adjust_inherited_tlist(List *tlist, @@ -46,18 +46,20 @@ static List *adjust_inherited_tlist(List *tlist, * Build an AppendRelInfo for the parent-child pair */ AppendRelInfo * -make_append_rel_info(Relation parentrel, Relation childrel, - Index parentRTindex, Index childRTindex) +make_append_rel_info(RelOptInfo *parent, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childtype, + Index childRTindex) { AppendRelInfo *appinfo = makeNode(AppendRelInfo); - appinfo->parent_relid = parentRTindex; + appinfo->parent_relid = parent->relid; appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->rd_rel->reltype; - appinfo->child_reltype = childrel->rd_rel->reltype; - make_inh_translation_list(parentrel, childrel, childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = RelationGetRelid(parentrel); + appinfo->parent_reltype = parent->reltype; + appinfo->child_reltype = childtype; + make_inh_translation_list(parent->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; return appinfo; } @@ -70,14 +72,11 @@ make_append_rel_info(Relation parentrel, Relation childrel, * For paranoia's sake, we match type/collation as well as attribute name. */ static void -make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, - List **translated_vars) +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) { List *vars = NIL; - TupleDesc old_tupdesc = RelationGetDescr(oldrelation); - TupleDesc new_tupdesc = RelationGetDescr(newrelation); - Oid new_relid = RelationGetRelid(newrelation); int oldnatts = old_tupdesc->natts; int newnatts = new_tupdesc->natts; int old_attno; @@ -107,7 +106,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, * When we are generating the "translation list" for the parent table * of an inheritance set, no need to search for matches. */ - if (oldrelation == newrelation) + if (from_rel == to_rel) { vars = lappend(vars, makeVar(newvarno, (AttrNumber) (old_attno + 1), @@ -133,10 +132,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, { HeapTuple newtup; - newtup = SearchSysCacheAttName(new_relid, attname); + newtup = SearchSysCacheAttName(to_rel, attname); if (!newtup) elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; ReleaseSysCache(newtup); @@ -146,10 +145,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, /* Found it, check type and collation match */ if (atttypid != att->atttypid || atttypmod != att->atttypmod) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); if (attcollation != att->attcollation) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); vars = lappend(vars, makeVar(newvarno, (AttrNumber) (new_attno + 1), diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index db474acbc5..bbf90c4c6c 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -18,30 +18,40 @@ #include "access/sysattr.h" #include "catalog/partition.h" #include "catalog/pg_inherits.h" +#include "catalog/pg_type.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" #include "optimizer/inherit.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" +#include "optimizer/var.h" +#include "partitioning/partprune.h" #include "utils/rel.h" - +static void expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti); static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); + Index rti, RelOptInfo *rel); static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); - +static RelOptInfo *build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); /* * expand_inherited_tables @@ -53,33 +63,134 @@ static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, void expand_inherited_tables(PlannerInfo *root) { - Index nrtes; + int orig_rtable_size; Index rti; - ListCell *rl; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; /* - * expand_inherited_rtentry may add RTEs to parse->rtable. The function is + * expand_append_rtentry may add RTEs to parse->rtable. The function is * expected to recursively handle any RTEs that it creates with inh=true. * So just scan as far as the original end of the rtable list. */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) + for (rti = 1; rti < orig_rtable_size; rti++) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + if (rte->inh) + expand_append_rtentry(root, brel, rte, rti); + } +} + +/* + * expand_append_rtentry + * This initializes RelOptInfos for an appendrel's child relations, if + * any + * + * 'rel' is the appendrel parent, whose range table entry ('rte') has been + * marked to require adding children. An appendrel parent could either + * be a subquery (if we flattened UNION ALL query) or a table that's known + * to have inheritance children. The latter consists of both regular + * inheritance parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as an appendrel child. The original RTE is + * considered to represent the whole inheritance set. + */ +static void +expand_append_rtentry(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte, + Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_append_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_inherited_rtentry(root, rte, rti, rel); } } /* * expand_inherited_rtentry - * Check whether a rangetable entry represents an inheritance set. - * If so, add entries for all the child tables to the query's - * rangetable, and build AppendRelInfo nodes for all the child tables - * and add them to root->append_rel_list. If not, clear the entry's - * "inh" flag to prevent later code from looking for AppendRelInfos. + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. * * Note that the original RTE is considered to represent the whole * inheritance set. The first of the generated RTEs is an RTE for the same @@ -90,55 +201,33 @@ expand_inherited_tables(PlannerInfo *root) * regular inheritance, a parent RTE must always have at least two associated * AppendRelInfos: one corresponding to the parent table as a simple member of * inheritance set and one or more corresponding to the actual children. - * Since a partitioned table is not scanned, it might have only one associated - * AppendRelInfo. */ static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, + RelOptInfo *rel) { Oid parentOID; PlanRowMark *oldrc; - Relation oldrelation; - LOCKMODE lockmode; + LOCKMODE lockmode = rte->rellockmode; List *inhOIDs; ListCell *l; + int num_children; + int num_children_added = 0; - /* Does RT entry allow inheritance? */ - if (!rte->inh) - return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } - /* Fast path for common case of childless table */ + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); parentOID = rte->relid; - if (!has_subclass(parentOID)) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * The rewriter should already have obtained an appropriate lock on each - * relation named in the query. However, for each child relation we add - * to the query, we must obtain an appropriate lock, because this will be - * the first use of those relations in the parse/rewrite/plan pipeline. - * Child rels should use the same lockmode as their parent. - */ - lockmode = rte->rellockmode; /* Scan for all members of inheritance set, acquire needed locks */ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); /* * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite above has_subclass() check, if table - * once had a child but no longer does. + * case. This could happen despite has_subclass() check performed by + * subquery_planner, if table once had a child but no longer does. */ - if (list_length(inhOIDs) < 2) + num_children = list_length(inhOIDs); + if (num_children < 2) { /* Clear flag before returning */ rte->inh = false; @@ -146,216 +235,219 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } /* - * If parent relation is selected FOR UPDATE/SHARE, we need to mark its - * PlanRowMark as isParent = true, and generate a new PlanRowMark for each - * child. + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. */ oldrc = get_plan_rowmark(root->rowMarks, rti); - if (oldrc) - oldrc->isParent = true; + Assert(oldrc == NULL || oldrc->isParent); /* - * Must open the parent relation to examine its tupdesc. We need not lock - * it; we assume the rewriter already did. + * Must expand PlannerInfo arrays by num_children before we can add + * children. */ - oldrelation = heap_open(parentOID, NoLock); + expand_planner_arrays(root, num_children); - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) + foreach(l, inhOIDs) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); - - /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. - */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); - } - else - { - List *appinfos = NIL; + Oid childOID = lfirst_oid(l); + Relation newrelation; RangeTblEntry *childrte; Index childRTindex; + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. */ - foreach(l, inhOIDs) + if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = heap_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - heap_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - heap_close(newrelation, NoLock); + heap_close(newrelation, lockmode); + continue; } - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); + (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation, &childrte, + &childRTindex); + Assert(childrte != NULL); + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + Assert(childRTindex > 0); + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; } - heap_close(oldrelation, NoLock); + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } } /* * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) + Index parentRTindex, RelOptInfo *parentrel) { + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; - check_stack_depth(); - - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + expand_planner_arrays(root, bms_num_members(partindexes)); /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. */ - if (partdesc->nparts == 0) - { - parentrte->inh = false; - return; - } + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); - for (i = 0; i < partdesc->nparts; i++) + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) { - Oid childOID = partdesc->oids[i]; - Relation childrel; + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + RangeTblEntry *childrte; + Index childRTindex; - /* Open rel; we already have required locks */ - childrel = heap_open(childOID, NoLock); + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation, + &childrte, &childRTindex); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + + /* If the child is partitioned itself, expand it too. */ + if (childrel->part_scheme) + { + Assert(childrte->inh); expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); + childrel); + } + } - /* Close child relation, but keep locks */ - heap_close(childrel, NoLock); + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); } } /* - * expand_single_inheritance_child - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) + * The return value is the RelOptInfo that's added. * * PlanRowMarks still carry the top-parent's RTI, and the top-parent's * allMarkTypes field still accumulates values from all descendents. * * "parentrte" and "parentRTindex" are immediate parent's RTE and * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. */ -static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); Oid childOID = RelationGetRelid(childrel); RangeTblEntry *childrte; Index childRTindex; AppendRelInfo *appinfo; + RelOptInfo *childrelopt; /* * Build an RTE for the child, and attach to query's rangetable list. We * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh = false. Also, set requiredPerms to zero since - * all required permissions checks are done on the original RTE. Likewise, - * set the child's securityQuals to empty, because we only want to apply - * the parent's RLS conditions regardless of what RLS properties + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties * individual children may have. (This is an intentional choice to make * inherited RLS work like regular permissions checks.) The parent * securityQuals will be propagated to children along with other base @@ -365,49 +457,42 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childrte_p = childrte; childrte->relid = childOID; childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) - childrte->inh = true; - else - childrte->inh = false; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); childrte->requiredPerms = 0; childrte->securityQuals = NIL; parse->rtable = lappend(parse->rtable, childrte); childRTindex = list_length(parse->rtable); *childRTindex_p = childRTindex; - /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. - */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) - { - appinfo = make_append_rel_info(parentrel, childrel, - parentRTindex, childRTindex); - *appinfos = lappend(*appinfos, appinfo); + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + RelationGetDescr(childrel), + RelationGetRelid(childrel), + RelationGetForm(childrel)->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); } /* @@ -439,6 +524,253 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need to create one, + * because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_append_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; +} + +/* + * build_append_child_rel + * Build a RelOptInfo for child relation of an append rel + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields based on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turn out to be self-contradictory, + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_append_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + List *childquals; + ListCell *lc; + bool have_const_false_cq; + Index cq_min_security; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = false; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, parent->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual = (Node *) rinfo->clause; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, childqual, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might + * have securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals.) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except + * that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* + * not likely that we'd see constants here, so no + * check + */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, + false, + false, + security_level, + NULL, NULL, + NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* Set child's version of baserestrictinfo. */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; } /* diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 6009de309e..293f9ac619 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -105,20 +105,20 @@ static void set_baserel_partition_key_exprs(Relation relation, * important for it. */ void -get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) +get_relation_info(PlannerInfo *root, RangeTblEntry *rte, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; bool hasindex; List *indexinfos = NIL; + bool inhparent = rte->inh; /* * We need not lock the relation since it was already locked, either by * the rewriter or when expand_inherited_rtentry() added it to the query's * rangetable. */ - relation = heap_open(relationObjectId, NoLock); + relation = heap_open(rte->relid, NoLock); /* Temporary and unlogged relations are inaccessible during recovery. */ if (!RelationNeedsWAL(relation) && RecoveryInProgress()) @@ -444,11 +444,32 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, get_relation_foreign_keys(root, rel, relation, inhparent); /* - * Collect info about relation's partitioning scheme, if any. Only - * inheritance parents may be partitioned. + * Collect some additional information for inheritance parents. */ - if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - set_relation_partition_info(root, rel, relation); + if (inhparent) + { + /* + * We'll need the TupleDesc when initializing the child relation. + * A copy is being made because concurrent changes might drop + * the relcache entry. That's possible because ALTER TABLE + * child_table NO INHERIT parent_table only requires an + * AccessShareLock on parent_table. + */ + rel->tupdesc = CreateTupleDescCopy(RelationGetDescr(relation)); + rel->reltype = RelationGetForm(relation)->reltype; + + /* + * If partitioned, also save the information of partitioning scheme, + * and whether the query updates any of the partition key columns. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + set_relation_partition_info(root, rel, relation); + root->partColsUpdated |= has_partition_attrs(relation, + rte->updatedCols, + NULL); + } + } heap_close(relation, NoLock); @@ -458,7 +479,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * removing an index, or adding a hypothetical index to the indexlist. */ if (get_relation_info_hook) - (*get_relation_info_hook) (root, relationObjectId, inhparent, rel); + (*get_relation_info_hook) (root, rte->relid, rte->inh, rel); } /* @@ -1844,16 +1865,20 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; + + /* + * Since we must've taken a lock on the table, it's okay to simply copy + * the pointers to relcache data here. + */ + rel->part_oids = partdesc->oids; + rel->boundinfo = partdesc->boundinfo; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 45f9950935..905fdc1419 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -131,6 +131,42 @@ setup_append_rel_array(PlannerInfo *root) } } +/* Expand the PlannerInfo arrays by add_size members and zero-init it. */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size = root->simple_rel_array_size + add_size; + + /* Expand various arrays and 0-initialize added bytes. */ + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + + if (root->append_rel_array) + { + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + } + else + { + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + } + + root->simple_rel_array_size = new_size; +} + /* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. @@ -236,7 +272,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte, rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -277,52 +313,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); - /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. - */ - if (rte->inh) - { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); - - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; - } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); - } - return rel; } diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 901433c68c..9bb472fd98 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -46,6 +46,7 @@ #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" #include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/planner.h" #include "optimizer/predtest.h" @@ -437,17 +438,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -541,23 +548,20 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ @@ -565,6 +569,13 @@ prune_append_rel_partitions(RelOptInfo *rel) return NULL; /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. */ @@ -591,15 +602,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index db439bd904..01a664d246 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -15,6 +15,7 @@ #define RELATION_H #include "access/sdir.h" +#include "access/tupdesc.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/params.h" @@ -729,6 +730,7 @@ typedef struct RelOptInfo int nparts; /* number of partitions */ struct PartitionBoundInfoData *boundinfo; /* Partition bounds */ List *partition_qual; /* partition constraint */ + Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ List **partexprs; /* Non-nullable partition key expressions. */ @@ -743,6 +745,13 @@ typedef struct RelOptInfo * RT index of UNION ALL parent subquery. */ Index inh_root_parent; + + /* + * Set only if this is an inheritance parent relation. This information + * is needed when initializing the planning info for children. + */ + TupleDesc tupdesc; /* A "copy" of the table's tuple desriptor */ + Oid reltype; /* Table's reltype */ } RelOptInfo; /* diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h index 156a0e077d..689d39517a 100644 --- a/src/include/optimizer/appendinfo.h +++ b/src/include/optimizer/appendinfo.h @@ -17,9 +17,10 @@ #include "nodes/relation.h" #include "utils/relcache.h" -extern AppendRelInfo *make_append_rel_info(Relation parentrel, - Relation childrel, - Index parentRTindex, Index childRTindex); +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parent, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childtype, + Index childRTindex); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index bd905d3328..e7d0a20c54 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -261,6 +261,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index a1b23251a1..d8091cdfa4 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -25,8 +25,8 @@ typedef void (*get_relation_info_hook_type) (PlannerInfo *root, extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; -extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); +extern void get_relation_info(PlannerInfo *root, RangeTblEntry *rte, + RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 62d45dd142..a329e87fc2 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -38,7 +38,8 @@ extern Expr *canonicalize_qual(Expr *qual, bool is_check); /* * prototypes for preptlist.c */ -extern List *preprocess_targetlist(PlannerInfo *root); +extern List *preprocess_targetlist(PlannerInfo *root, + bool inheritance_expanded); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h index ce9bc8d9fd..5c5d27d040 100644 --- a/src/include/partitioning/partprune.h +++ b/src/include/partitioning/partprune.h @@ -76,7 +76,7 @@ extern PartitionPruneInfo *make_partition_pruneinfo(PlannerInfo *root, List *subpaths, List *partitioned_rels, List *prunequal); -extern Relids prune_append_rel_partitions(RelOptInfo *rel); +extern Bitmapset *prune_append_rel_partitions(RelOptInfo *rel); extern Bitmapset *get_matching_partitions(PartitionPruneContext *context, List *pruning_steps); diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 6bc106831e..1450cef057 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) -- 2.11.0
v13-0005-Teach-planner-to-only-process-unpruned-partition.patch (text/plain, 6.9K) download From 717cdd515f4a135370460b2288fe2e0c29608b06 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 15 Oct 2018 10:59:24 +0900 Subject: [PATCH v13 5/6] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/inherit.c | 1 + src/backend/optimizer/util/relnode.c | 3 +++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/relation.h | 4 ++++ 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 411cdbd84f..eb0e918d05 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1426,6 +1426,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 6ea2fca611..6591d46c8f 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6824,7 +6824,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -6832,9 +6834,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -6915,7 +6915,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -6927,7 +6926,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -6937,9 +6938,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index bbf90c4c6c..ac2217a742 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -348,6 +348,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, /* Perform pruning. */ partindexes = prune_append_rel_partitions(parentrel); + parentrel->live_parts = partindexes; /* Must expand PlannerInfo arrays before we can add children. */ expand_planner_arrays(root, bms_num_members(partindexes)); diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 905fdc1419..fa52aaa6de 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1749,6 +1749,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 9bb472fd98..088b191bdd 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -441,29 +441,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 01a664d246..d27611c2c7 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -733,6 +733,10 @@ typedef struct RelOptInfo Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Indexes into part_rels of the non-NULL + * RelOptInfos of unpruned partitions; exists + * to avoid having to iterate over the entire + * part_rels array to filter NULL entries. */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.11.0
v13-0006-Do-not-lock-all-partitions-at-the-beginning.patch (text/plain, 1.8K) download From 3ad1e4668b168beae60a5999019457958ee52916 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 12 Oct 2018 19:12:53 +0900 Subject: [PATCH v13 6/6] Do not lock all partitions at the beginning --- src/backend/optimizer/util/inherit.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index ac2217a742..9d64abdce7 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -315,10 +315,6 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti, * * Partitions are added to the query in order in which they are found in * the parent's PartitionDesc. - * - * Note: even though only the unpruned partitions will be added to the - * resulting plan, this still locks *all* partitions via find_all_inheritors - * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, @@ -332,10 +328,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, parentrel->inh_root_parent : parentRTindex; - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - /* * Initialize partitioned_child_rels to contain this RT index. * @@ -371,8 +363,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, RangeTblEntry *childrte; Index childRTindex; - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); + newrelation = heap_open(childOID, lockmode); Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-12 08:49  David Rowley <david.rowley@2ndquadrant.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 2 replies; 265+ messages in thread

From: David Rowley @ 2019-01-12 08:49 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On Sat, 12 Jan 2019 at 02:00, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> That's an oversight.  Fixed by making add_child_rel_equivalences do this:
>
> +            cur_ec->ec_relids = bms_difference(cur_ec->ec_relids,
> +                                               parent_rel->relids);
> +            cur_ec->ec_relids = bms_add_members(cur_ec->ec_relids,
> +                                                child_rel->relids);
>
> >
> > UPDATE: I see you're likely leaving this alone since you're only doing
> > a shallow copy of the eclasses in
> > adjust_inherited_target_child_root(). It seems like a pretty bad idea
> > to do a shallow copy there.
>
> So, you're talking about this code:
>
>     /*
>      * Child root should get its own copy of ECs, because they'll be modified
>      * to replace parent EC expressions by child expressions in
>      * add_child_rel_equivalences.
>      */
>     subroot->eq_classes = NIL;
>     foreach(lc, root->eq_classes)
>     {
>         EquivalenceClass *ec = lfirst(lc);
>         EquivalenceClass *new_ec = makeNode(EquivalenceClass);
>
>         memcpy(new_ec, ec, sizeof(EquivalenceClass));
>         new_ec->ec_members = list_copy(ec->ec_members);
>         subroot->eq_classes = lappend(subroot->eq_classes, new_ec);
>     }
>
> Can you say what you think is wrong with this way of making a copy of the ECs?

If you shallow copy with memcpy(new_ec, ec,
sizeof(EquivalenceClass));, then fields such as ec_relids will just
point to the same memory as the parent PlannerInfo's
EquivalenceClasses, so when you do your adjustment (as above) on the
child eclasses, you'll modify memory belonging to the parent. I'd
assume you'd not want to do that since you need to keep the parent
intact at least to make copies for other child rels.  You'd have
gotten away with it before since you performed a list_copy() and only
were deleting the parent's EMs with list_delete_cell() which was
modifying the copy, not the original list. Since you were missing the
alteration to ec_relids, then you might not have seen issues with the
shallow copy, but now that you are changing that field, are you not
modifying the ec_relids field that still set in the parent's
PlannerInfo?  In this instance, you've sidestepped that issue by using
bms_difference() which creates a copy of the Bitmapset and modifies
that. I think you'd probably see issues if you tried to use
bms_del_members().  I think not doing the deep copy is going to give
other people making changes in this are a hard time in the future.

> > 12. The following comment makes less sense now that you've modified
> > the previous paragraph:
> >
> > + * Fortunately, the UPDATE/DELETE target can never be the nullable side of an
> > + * outer join, so it's OK to generate the plan this way.
> >
> > This text used to refer to:
> >
> > but target inheritance has to be expanded at
> >  * the top.  The reason is that for UPDATE, each target relation needs a
> >  * different targetlist matching its own column set.  Fortunately,
> >  * the UPDATE/DELETE target can never be the nullable side of an outer join,
> >  * so it's OK to generate the plan this way.
> >
> > you no longer describe plan as being expanded from the top rather than
> > at the bottom, which IMO is what "this way" refers to.
>
> To be honest, I never quite understood what that line really means, which
> is why I kept it around.  What I do know though is that, even with the
> patched, we still generate subpaths for each child whose targetlist
> patches the child, so I think the part about "this way" that prompted
> someone to write that line still remains.  Does that make sense to you?

Not quite.  I thought that "OK to generate the plan this way" is
talking about expanding the children at the top of the plan, e.g.

explain (costs off) update listp set b = b + 1 from t where listp.a = t.a;
              QUERY PLAN
--------------------------------------
 Update on listp
   Update on listp1
   Update on listp2
   ->  Merge Join
         Merge Cond: (listp1.a = t.a)
         ->  Sort
               Sort Key: listp1.a
               ->  Seq Scan on listp1
         ->  Sort
               Sort Key: t.a
               ->  Seq Scan on t
   ->  Merge Join
         Merge Cond: (listp2.a = t.a)
         ->  Sort
               Sort Key: listp2.a
               ->  Seq Scan on listp2
         ->  Sort
               Sort Key: t.a
               ->  Seq Scan on t

i.e each non-pruned partition gets joined to the other tables
(expanded from the top of the plan tree). The other tables appear once
for each child target relation.

instead of:

postgres=# explain (costs off) select * from listp inner join t on
listp.a = t.a;
              QUERY PLAN
--------------------------------------
 Merge Join
   Merge Cond: (t.a = listp1.a)
   ->  Sort
         Sort Key: t.a
         ->  Seq Scan on t
   ->  Sort
         Sort Key: listp1.a
         ->  Append
               ->  Seq Scan on listp1
               ->  Seq Scan on listp2

where the children are expanded from the bottom (or at the leaf side
of the plan tree), since we always plant our trees up-side-down, in
computer science.

In my view, since you removed the part about where the children are
expanded then it does not quite make sense anymore.

Of course, I might not be reading the comment as it was intended.

-- 
 David Rowley                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-15 05:54  David Rowley <david.rowley@2ndquadrant.com>
  parent: David Rowley <david.rowley@2ndquadrant.com>
  1 sibling, 0 replies; 265+ messages in thread

From: David Rowley @ 2019-01-15 05:54 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On Sat, 12 Jan 2019 at 21:49, David Rowley <david.rowley@2ndquadrant.com> wrote:
> > Can you say what you think is wrong with this way of making a copy of the ECs?
>
> If you shallow copy with memcpy(new_ec, ec,
> sizeof(EquivalenceClass));, then fields such as ec_relids will just
> point to the same memory as the parent PlannerInfo's
> EquivalenceClasses, so when you do your adjustment (as above) on the
> child eclasses, you'll modify memory belonging to the parent. I'd
> assume you'd not want to do that since you need to keep the parent
> intact at least to make copies for other child rels.  You'd have
> gotten away with it before since you performed a list_copy() and only
> were deleting the parent's EMs with list_delete_cell() which was
> modifying the copy, not the original list. Since you were missing the
> alteration to ec_relids, then you might not have seen issues with the
> shallow copy, but now that you are changing that field, are you not
> modifying the ec_relids field that still set in the parent's
> PlannerInfo?  In this instance, you've sidestepped that issue by using
> bms_difference() which creates a copy of the Bitmapset and modifies
> that. I think you'd probably see issues if you tried to use
> bms_del_members().  I think not doing the deep copy is going to give
> other people making changes in this are a hard time in the future.

Setting to waiting on author pending clarification about shallow vs
deep copying of ECs.

-- 
 David Rowley                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-16 06:22  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: David Rowley <david.rowley@2ndquadrant.com>
  1 sibling, 0 replies; 265+ messages in thread

From: Amit Langote @ 2019-01-16 06:22 UTC (permalink / raw)
  To: David Rowley <david.rowley@2ndquadrant.com>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Thanks for the comments.

On 2019/01/12 17:49, David Rowley wrote:
> On Sat, 12 Jan 2019 at 02:00, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> That's an oversight.  Fixed by making add_child_rel_equivalences do this:
>>
>> +            cur_ec->ec_relids = bms_difference(cur_ec->ec_relids,
>> +                                               parent_rel->relids);
>> +            cur_ec->ec_relids = bms_add_members(cur_ec->ec_relids,
>> +                                                child_rel->relids);
>>
>>>
>>> UPDATE: I see you're likely leaving this alone since you're only doing
>>> a shallow copy of the eclasses in
>>> adjust_inherited_target_child_root(). It seems like a pretty bad idea
>>> to do a shallow copy there.
>>
>> So, you're talking about this code:
>>
>>     /*
>>      * Child root should get its own copy of ECs, because they'll be modified
>>      * to replace parent EC expressions by child expressions in
>>      * add_child_rel_equivalences.
>>      */
>>     subroot->eq_classes = NIL;
>>     foreach(lc, root->eq_classes)
>>     {
>>         EquivalenceClass *ec = lfirst(lc);
>>         EquivalenceClass *new_ec = makeNode(EquivalenceClass);
>>
>>         memcpy(new_ec, ec, sizeof(EquivalenceClass));
>>         new_ec->ec_members = list_copy(ec->ec_members);
>>         subroot->eq_classes = lappend(subroot->eq_classes, new_ec);
>>     }
>>
>> Can you say what you think is wrong with this way of making a copy of the ECs?
> 
> If you shallow copy with memcpy(new_ec, ec,
> sizeof(EquivalenceClass));, then fields such as ec_relids will just
> point to the same memory as the parent PlannerInfo's
> EquivalenceClasses, so when you do your adjustment (as above) on the
> child eclasses, you'll modify memory belonging to the parent. I'd
> assume you'd not want to do that since you need to keep the parent
> intact at least to make copies for other child rels.  You'd have
> gotten away with it before since you performed a list_copy() and only
> were deleting the parent's EMs with list_delete_cell() which was
> modifying the copy, not the original list. Since you were missing the
> alteration to ec_relids, then you might not have seen issues with the
> shallow copy, but now that you are changing that field, are you not
> modifying the ec_relids field that still set in the parent's
> PlannerInfo?

Yep.  add_eq_member, when adding the child member to the child's copy of
EC, will end up modifying ec_relids that it shares with the parent's copy,
because it uses bms_add_member which modifies the input bitmapset in-place.

> In this instance, you've sidestepped that issue by using
> bms_difference() which creates a copy of the Bitmapset and modifies
> that. I think you'd probably see issues if you tried to use
> bms_del_members().  I think not doing the deep copy is going to give
> other people making changes in this are a hard time in the future.

I thought of inventing a _copyEquivalenceClass but noticed this in
_copyPathKey:

    /* EquivalenceClasses are never moved, so just shallow-copy the pointer */
    COPY_SCALAR_FIELD(pk_eclass);

I think that won't be a problem in our case, because this comment seems to
be talking about identical copies of a given EC.  In our case, we're
talking about making copies that are essentially different because of
parent-to-child translation.  However, providing a function in copyfuncs.c
to copy ECs would make it sound like making identical copies of ECs is OK
(which it apparently isn't), so I added the copying code in the place
where the patch wants to use it.  The new code takes care to make copies
of all the fields that might change, not just ec_members.

Also, realizing another problem with where the copying code is placed now
in the patch (adjust_inherited_target_child_root), I have moved it to
adjust_inherit_target_rel_sizes so that translation from parent-to-child
of EC members works correctly.  Concretely, the problem is that a level 2
partition's EC members won't be created with the patch's current way of
initializing eq_classes in the partition subroots -- all initially get the
copy of root parent's EC members, whereas translation can only occur
between a partition and its immediate parent.  Without EC members, a level
2 leaf partition will not be able to merge join.  So, for example:

explain update p set a = foo.a+1 from foo where p.a = foo.a;
                                QUERY PLAN
──────────────────────────────────────────────────────────────────────────
 Update on p  (cost=0.00..98556.15 rows=6535012 width=16)
   Update on p11
   Update on p2
   ->  Nested Loop  (cost=0.00..97614.88 rows=6502500 width=16)
         ->  Seq Scan on p11  (cost=0.00..35.50 rows=2550 width=10)
         ->  Materialize  (cost=0.00..48.25 rows=2550 width=10)
               ->  Seq Scan on foo  (cost=0.00..35.50 rows=2550 width=10)
   ->  Merge Join  (cost=359.57..941.28 rows=32512 width=16)
         Merge Cond: (p2.a = foo.a)
         ->  Sort  (cost=179.78..186.16 rows=2550 width=10)
               Sort Key: p2.a
               ->  Seq Scan on p2  (cost=0.00..35.50 rows=2550 width=10)
         ->  Sort  (cost=179.78..186.16 rows=2550 width=10)
               Sort Key: foo.a
               ->  Seq Scan on foo  (cost=0.00..35.50 rows=2550 width=10)

vs.

explain update p set a = foo.a+1 from foo where p.a = foo.a;
                                QUERY PLAN
──────────────────────────────────────────────────────────────────────────
 Update on p  (cost=359.57..1882.55 rows=65024 width=16)
   Update on p11
   Update on p2
   ->  Merge Join  (cost=359.57..941.28 rows=32512 width=16)
         Merge Cond: (p11.a = foo.a)
         ->  Sort  (cost=179.78..186.16 rows=2550 width=10)
               Sort Key: p11.a
               ->  Seq Scan on p11  (cost=0.00..35.50 rows=2550 width=10)
         ->  Sort  (cost=179.78..186.16 rows=2550 width=10)
               Sort Key: foo.a
               ->  Seq Scan on foo  (cost=0.00..35.50 rows=2550 width=10)
   ->  Merge Join  (cost=359.57..941.28 rows=32512 width=16)
         Merge Cond: (p2.a = foo.a)
         ->  Sort  (cost=179.78..186.16 rows=2550 width=10)
               Sort Key: p2.a
               ->  Seq Scan on p2  (cost=0.00..35.50 rows=2550 width=10)
         ->  Sort  (cost=179.78..186.16 rows=2550 width=10)
               Sort Key: foo.a
               ->  Seq Scan on foo  (cost=0.00..35.50 rows=2550 width=10)
(19 rows)

after fixing.  Note that p11 is a partition of p1 which is partition of p.
 p2 is partition of p.

>>> 12. The following comment makes less sense now that you've modified
>>> the previous paragraph:
>>>
>>> + * Fortunately, the UPDATE/DELETE target can never be the nullable side of an
>>> + * outer join, so it's OK to generate the plan this way.
>>>
>>> This text used to refer to:
>>>
>>> but target inheritance has to be expanded at
>>>  * the top.  The reason is that for UPDATE, each target relation needs a
>>>  * different targetlist matching its own column set.  Fortunately,
>>>  * the UPDATE/DELETE target can never be the nullable side of an outer join,
>>>  * so it's OK to generate the plan this way.
>>>
>>> you no longer describe plan as being expanded from the top rather than
>>> at the bottom, which IMO is what "this way" refers to.
>>
>> To be honest, I never quite understood what that line really means, which
>> is why I kept it around.  What I do know though is that, even with the
>> patched, we still generate subpaths for each child whose targetlist
>> patches the child, so I think the part about "this way" that prompted
>> someone to write that line still remains.  Does that make sense to you?
> 
> Not quite.  I thought that "OK to generate the plan this way" is
> talking about expanding the children at the top of the plan, e.g.
> 
> explain (costs off) update listp set b = b + 1 from t where listp.a = t.a;
>               QUERY PLAN
> --------------------------------------
>  Update on listp
>    Update on listp1
>    Update on listp2
>    ->  Merge Join
>          Merge Cond: (listp1.a = t.a)
>          ->  Sort
>                Sort Key: listp1.a
>                ->  Seq Scan on listp1
>          ->  Sort
>                Sort Key: t.a
>                ->  Seq Scan on t
>    ->  Merge Join
>          Merge Cond: (listp2.a = t.a)
>          ->  Sort
>                Sort Key: listp2.a
>                ->  Seq Scan on listp2
>          ->  Sort
>                Sort Key: t.a
>                ->  Seq Scan on t
> 
> i.e each non-pruned partition gets joined to the other tables
> (expanded from the top of the plan tree). The other tables appear once
> for each child target relation.
> 
> instead of:
> 
> postgres=# explain (costs off) select * from listp inner join t on
> listp.a = t.a;
>               QUERY PLAN
> --------------------------------------
>  Merge Join
>    Merge Cond: (t.a = listp1.a)
>    ->  Sort
>          Sort Key: t.a
>          ->  Seq Scan on t
>    ->  Sort
>          Sort Key: listp1.a
>          ->  Append
>                ->  Seq Scan on listp1
>                ->  Seq Scan on listp2
> 
> where the children are expanded from the bottom (or at the leaf side
> of the plan tree), since we always plant our trees up-side-down, in
> computer science.
> 
> In my view, since you removed the part about where the children are
> expanded then it does not quite make sense anymore.

Thanks for explaining.  I think my previous reply was confusing -- I
wanted to say that we *do* still expand the children at the top, in the
sense that the resulting plan shape hasn't changed due to the patch.   I
suspect that it's the resulting plan shape that "generating plan this way"
refers to, which hasn't changed with the patch.  It's true that the patch
optimizes away repeated query_planner calls but only because it makes
other arrangements to produce the same output as before.

Attached updated patches with mainly fixes around EC copying as described
above and other cosmetic fixes like comment updates.  I've also moved
around the code a bit, putting the new functions
add_inherit_target_child_roots, etc. in inherit.c instead of planmain.c.

Thanks,
Amit

Attachments: download all (6 files, .tar.gz)

0001-Some-fixups-for-b60c397599-v2.patch (text/plain, 11.4K) download From ea4433237227778676f3bc7bfc9fa3609f240f8d Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 11 Jan 2019 11:11:30 +0900 Subject: [PATCH v14 1/6] Some fixups for b60c397599 (v2) --- src/backend/optimizer/path/joinrels.c | 45 ++++++++++++++ src/backend/optimizer/util/appendinfo.c | 101 +------------------------------- src/backend/optimizer/util/inherit.c | 55 +++++++++++++++++ src/include/optimizer/appendinfo.h | 10 +--- 4 files changed, 104 insertions(+), 107 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 38eeb23d81..db18feccfe 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -46,6 +46,9 @@ static void try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, List *parent_restrictlist); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, + SpecialJoinInfo *parent_sjinfo, + Relids left_relids, Relids right_relids); /* @@ -1582,3 +1585,45 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * Construct the SpecialJoinInfo for a child-join by translating + * SpecialJoinInfo for the join between parents. left_relids and right_relids + * are the relids of left and right side of the join respectively. + */ +static SpecialJoinInfo * +build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, + Relids left_relids, Relids right_relids) +{ + SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo); + AppendRelInfo **left_appinfos; + int left_nappinfos; + AppendRelInfo **right_appinfos; + int right_nappinfos; + + memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo)); + left_appinfos = find_appinfos_by_relids(root, left_relids, + &left_nappinfos); + right_appinfos = find_appinfos_by_relids(root, right_relids, + &right_nappinfos); + + sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand, + left_nappinfos, left_appinfos); + sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand, + right_nappinfos, + right_appinfos); + sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand, + left_nappinfos, left_appinfos); + sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand, + right_nappinfos, + right_appinfos); + sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root, + (Node *) sjinfo->semi_rhs_exprs, + right_nappinfos, + right_appinfos); + + pfree(left_appinfos); + pfree(right_appinfos); + + return sjinfo; +} diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index d48e3a09b3..37fbc3a117 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -15,13 +15,12 @@ #include "postgres.h" #include "access/htup_details.h" -#include "access/sysattr.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" #include "parser/parsetree.h" -#include "utils/rel.h" #include "utils/lsyscache.h" +#include "utils/rel.h" #include "utils/syscache.h" @@ -38,8 +37,6 @@ static void make_inh_translation_list(Relation oldrelation, List **translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, adjust_appendrel_attrs_context *context); -static Relids adjust_child_relids(Relids relids, int nappinfos, - AppendRelInfo **appinfos); static List *adjust_inherited_tlist(List *tlist, AppendRelInfo *context); @@ -167,58 +164,6 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, } /* - * translate_col_privs - * Translate a bitmapset representing per-column privileges from the - * parent rel's attribute numbering to the child's. - * - * The only surprise here is that we don't translate a parent whole-row - * reference into a child whole-row reference. That would mean requiring - * permissions on all child columns, which is overly strict, since the - * query is really only going to reference the inherited columns. Instead - * we set the per-column bits for all inherited columns. - */ -Bitmapset * -translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars) -{ - Bitmapset *child_privs = NULL; - bool whole_row; - int attno; - ListCell *lc; - - /* System attributes have the same numbers in all tables */ - for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) - { - if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - attno - FirstLowInvalidHeapAttributeNumber); - } - - /* Check if parent has whole-row reference */ - whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, - parent_privs); - - /* And now translate the regular user attributes, using the vars list */ - attno = InvalidAttrNumber; - foreach(lc, translated_vars) - { - Var *var = lfirst_node(Var, lc); - - attno++; - if (var == NULL) /* ignore dropped columns */ - continue; - if (whole_row || - bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, - parent_privs)) - child_privs = bms_add_member(child_privs, - var->varattno - FirstLowInvalidHeapAttributeNumber); - } - - return child_privs; -} - -/* * adjust_appendrel_attrs * Copy the specified query or expression and translate Vars referring to a * parent rel to refer to the corresponding child rel instead. We also @@ -531,7 +476,7 @@ adjust_appendrel_attrs_mutator(Node *node, * Substitute child relids for parent relids in a Relid set. The array of * appinfos specifies the substitutions to be performed. */ -static Relids +Relids adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos) { Bitmapset *result = NULL; @@ -754,48 +699,6 @@ adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, } /* - * Construct the SpecialJoinInfo for a child-join by translating - * SpecialJoinInfo for the join between parents. left_relids and right_relids - * are the relids of left and right side of the join respectively. - */ -SpecialJoinInfo * -build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, - Relids left_relids, Relids right_relids) -{ - SpecialJoinInfo *sjinfo = makeNode(SpecialJoinInfo); - AppendRelInfo **left_appinfos; - int left_nappinfos; - AppendRelInfo **right_appinfos; - int right_nappinfos; - - memcpy(sjinfo, parent_sjinfo, sizeof(SpecialJoinInfo)); - left_appinfos = find_appinfos_by_relids(root, left_relids, - &left_nappinfos); - right_appinfos = find_appinfos_by_relids(root, right_relids, - &right_nappinfos); - - sjinfo->min_lefthand = adjust_child_relids(sjinfo->min_lefthand, - left_nappinfos, left_appinfos); - sjinfo->min_righthand = adjust_child_relids(sjinfo->min_righthand, - right_nappinfos, - right_appinfos); - sjinfo->syn_lefthand = adjust_child_relids(sjinfo->syn_lefthand, - left_nappinfos, left_appinfos); - sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand, - right_nappinfos, - right_appinfos); - sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root, - (Node *) sjinfo->semi_rhs_exprs, - right_nappinfos, - right_appinfos); - - pfree(left_appinfos); - pfree(right_appinfos); - - return sjinfo; -} - -/* * find_appinfos_by_relids * Find AppendRelInfo structures for all relations specified by relids. * diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 350e6afe27..db474acbc5 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -15,6 +15,7 @@ #include "postgres.h" #include "access/heapam.h" +#include "access/sysattr.h" #include "catalog/partition.h" #include "catalog/pg_inherits.h" #include "miscadmin.h" @@ -38,6 +39,8 @@ static void expand_single_inheritance_child(PlannerInfo *root, PlanRowMark *top_parentrc, Relation childrel, List **appinfos, RangeTblEntry **childrte_p, Index *childRTindex_p); +static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars); /* @@ -437,3 +440,55 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } } + +/* + * translate_col_privs + * Translate a bitmapset representing per-column privileges from the + * parent rel's attribute numbering to the child's. + * + * The only surprise here is that we don't translate a parent whole-row + * reference into a child whole-row reference. That would mean requiring + * permissions on all child columns, which is overly strict, since the + * query is really only going to reference the inherited columns. Instead + * we set the per-column bits for all inherited columns. + */ +static Bitmapset * +translate_col_privs(const Bitmapset *parent_privs, + List *translated_vars) +{ + Bitmapset *child_privs = NULL; + bool whole_row; + int attno; + ListCell *lc; + + /* System attributes have the same numbers in all tables */ + for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++) + { + if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + attno - FirstLowInvalidHeapAttributeNumber); + } + + /* Check if parent has whole-row reference */ + whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, + parent_privs); + + /* And now translate the regular user attributes, using the vars list */ + attno = InvalidAttrNumber; + foreach(lc, translated_vars) + { + Var *var = lfirst_node(Var, lc); + + attno++; + if (var == NULL) /* ignore dropped columns */ + continue; + if (whole_row || + bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, + parent_privs)) + child_privs = bms_add_member(child_privs, + var->varattno - FirstLowInvalidHeapAttributeNumber); + } + + return child_privs; +} diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h index 16705da780..156a0e077d 100644 --- a/src/include/optimizer/appendinfo.h +++ b/src/include/optimizer/appendinfo.h @@ -20,21 +20,15 @@ extern AppendRelInfo *make_append_rel_info(Relation parentrel, Relation childrel, Index parentRTindex, Index childRTindex); -extern Bitmapset *translate_col_privs(const Bitmapset *parent_privs, - List *translated_vars); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); - extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, Relids child_relids, Relids top_parent_relids); - extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos); - -extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, - SpecialJoinInfo *parent_sjinfo, - Relids left_relids, Relids right_relids); +extern Relids adjust_child_relids(Relids relids, int nappinfos, + AppendRelInfo **appinfos); extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, Relids child_relids, Relids top_parent_relids); -- 2.11.0
v14-0002-Overhaul-inheritance-update-delete-planning.patch (text/plain, 75.0K) download From 07939e55d4574645308354963b6e86e2eedd033f Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 11:18:12 +0900 Subject: [PATCH v14 2/6] Overhaul inheritance update/delete planning With the current method, inheritance_planner, which handles the planning for update/delete commands targeting inheritance trees, repeatedly calls query_planner for each child table. That's inefficient for two reasons: 1. partprune.c cannot be used to perform partition pruning, because it can only be invoked from query_planner. With the current approach, query_planner only sees the individual partitions in the query tree it receives, not the parent partitioned table. This leaves each partition to be pruned using constraint exclusion. (constraint exclusion cannon prune hash partitions which means UPDATE/DELETE don't support hash partition pruning.) 2. Repeated invocation of query_planner results in allocating large amounts of memory, especially if there are many child tables. Also, it involves repeatedly performing the same processing, such as jointree processing. This commit addresses both of the issues by modifying query_planner to handle the inheritance target case directly. Instead of having the caller create separate PlannerInfo's for individual child target relations and pass them to query_planner (actually, via grouping_planner), this teaches query_planner itself to create the child PlannerInfo's by the original parent PlannerInfo. It creates them *after* it has generated enough state in the latter, such as RelOptInfos for various relations, EquivalenceClasses after processing the join tree, and *before* generating the scan and join paths. Scan paths are generated normally, except that Paths of target children are not "appended". Join paths are generated separately for each child target relation, that is, by replacing the parent relation references in the original join tree with the child relation references. Joinrels (RelOptInfos thereof) corresponding to various child target relation are saved in the parent PlannerInfo for further processing. inheritance_planner now calls query_planner just once at the beginning with the original unmodified query, which takes care of generating top-level join rels for the child queries as described above. For each child join rel, it then calls grouping_planner using previously created child PlannerInfo to finish up the paths such that they produce query's top-level target list expanded according to a given child relation's descriptor. grouping_planner()'s interface is modified so that we can pass it what's called a 'planned_rel', a RelOptInfo that already contains the necessary paths needed to produce its output. This removes some existing code in inheritance_planner that dealt with any subquery RTEs in the query. The rationale of that code was that the subquery RTEs may change during each iteration of planning (that is, for different children), so different iterations better use different copies of those RTEs. That was handled by making fresh copies of those RTEs for each iteration of planning which were appended to the range table, accompanied by modifying all expressions and auxiliary structures that referenced the original subquery RTEs to instead reference the copies (that is, change the varnos). This copying meant we would end up adding S * N new entries to the original range table by the time we got to the last unpruned child, where S is the number of subquery RTEs in the original query and N the number of unpruned children. Since with the new code we perform planning just once, I think we don't need this special handling. Actually, there is a regression test output change due to no longer having copies of subquery RTEs (see the diff of partition_join.out file.) Also we no longer need the hack in relation_excluded_by_constraints() involving setting PlannerInfo.inhTargetKind to enable constraint exclusion for target child relation, which is no longer needed. Constraint exclusion now runs during query_planner step described above. Regression test output change in partition_join.out is due to the fact that we no longer create duplicates of subquery RTEs in child range tables. --- doc/src/sgml/ddl.sgml | 17 +- src/backend/nodes/outfuncs.c | 2 +- src/backend/optimizer/path/allpaths.c | 614 +++++++++++++++++++++++++-- src/backend/optimizer/path/equivclass.c | 57 ++- src/backend/optimizer/plan/createplan.c | 10 - src/backend/optimizer/plan/planmain.c | 48 ++- src/backend/optimizer/plan/planner.c | 444 ++++++------------- src/backend/optimizer/prep/prepjointree.c | 1 - src/backend/optimizer/util/appendinfo.c | 33 +- src/backend/optimizer/util/inherit.c | 109 +++++ src/backend/optimizer/util/plancat.c | 73 +--- src/include/nodes/relation.h | 37 +- src/include/optimizer/inherit.h | 1 + src/include/optimizer/paths.h | 3 +- src/test/regress/expected/partition_join.out | 4 +- 15 files changed, 988 insertions(+), 465 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 85e4358988..1bb5ca6b08 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -4439,24 +4439,11 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <note> <para> - Currently, pruning of partitions during the planning of an - <command>UPDATE</command> or <command>DELETE</command> command is - implemented using the constraint exclusion method (however, it is - controlled by the <literal>enable_partition_pruning</literal> rather than - <literal>constraint_exclusion</literal>) &mdash; see the following section - for details and caveats that apply. - </para> - - <para> Execution-time partition pruning currently only occurs for the <literal>Append</literal> and <literal>MergeAppend</literal> node types. It is not yet implemented for the <literal>ModifyTable</literal> node - type. - </para> - - <para> - Both of these behaviors are likely to be changed in a future release - of <productname>PostgreSQL</productname>. + type, but that is likely to be changed in a future release of + <productname>PostgreSQL</productname>. </para> </note> </sect2> diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 0fde876c77..158da68925 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2210,7 +2210,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); WRITE_UINT_FIELD(qual_security_level); - WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind); WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasDeletedRTEs); @@ -2221,6 +2220,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_BITMAPSET_FIELD(curOuterRels); WRITE_NODE_FIELD(curOuterParams); WRITE_BOOL_FIELD(partColsUpdated); + WRITE_BOOL_FIELD(inherited_update); } static void diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index bc389b52e4..3324d485e4 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -38,6 +38,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" @@ -92,8 +93,12 @@ static void set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); +static void set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte); static void set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); +static void set_inherit_target_rel_pathlists(PlannerInfo *root, + RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, @@ -120,6 +125,8 @@ static void set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); +static void inheritance_make_rel_from_joinlist(PlannerInfo *root, + List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, pushdown_safety_info *safetyInfo); static bool recurse_pushdown_safe(Node *setOp, Query *topquery, @@ -150,27 +157,6 @@ make_one_rel(PlannerInfo *root, List *joinlist) Index rti; double total_pages; - /* - * Construct the all_baserels Relids set. - */ - root->all_baserels = NULL; - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - - /* there may be empty slots corresponding to non-baserel RTEs */ - if (brel == NULL) - continue; - - Assert(brel->relid == rti); /* sanity check on array */ - - /* ignore RTEs that are "other rels" */ - if (brel->reloptkind != RELOPT_BASEREL) - continue; - - root->all_baserels = bms_add_member(root->all_baserels, brel->relid); - } - /* Mark base rels as to whether we care about fast-start plans */ set_base_rel_consider_startup(root); @@ -218,13 +204,34 @@ make_one_rel(PlannerInfo *root, List *joinlist) /* * Generate access paths for the entire join tree. + * + * For UPDATE/DELETE on an inheritance parent, join paths should be + * generated for each child result rel separately. */ - rel = make_rel_from_joinlist(root, joinlist); + if (root->parse->resultRelation && + root->simple_rte_array[root->parse->resultRelation]->inh) + { + inheritance_make_rel_from_joinlist(root, joinlist); - /* - * The result should join all and only the query's base rels. - */ - Assert(bms_equal(rel->relids, root->all_baserels)); + /* + * Return the RelOptInfo of original target relation, although this + * doesn't really contain the final path. inheritance_planner + * from where we got here will generate the final path, but it will + * do so by iterative over child subroots, not through this + * RelOptInfo. + */ + rel = find_base_rel(root, root->parse->resultRelation); + } + else + { + rel = make_rel_from_joinlist(root, joinlist); + + /* + * The result should join all and only the query's base rels. + */ + Assert(bms_equal(rel->relids, root->all_baserels)); + + } return rel; } @@ -308,9 +315,10 @@ set_base_rel_sizes(PlannerInfo *root) * If parallelism is allowable for this query in general, see whether * it's allowable for this rel in particular. We have to do this * before set_rel_size(), because (a) if this rel is an inheritance - * parent, set_append_rel_size() will use and perhaps change the rel's - * consider_parallel flag, and (b) for some RTE types, set_rel_size() - * goes ahead and makes paths immediately. + * parent, set_append_rel_size() or set_inherit_target_rel_sizes() + * will use and perhaps change the rel's consider_parallel flag, and + * (b) for some RTE types, set_rel_size() goes ahead and makes paths + * immediately. */ if (root->glob->parallelModeOK) set_rel_consider_parallel(root, rel, rte); @@ -374,8 +382,15 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_size(root, rel, rti, rte); + /* + * If it's a target relation, set the sizes of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (rti == root->parse->resultRelation) + set_inherit_target_rel_sizes(root, rel, rti, rte); + else + set_append_rel_size(root, rel, rti, rte); } else { @@ -459,14 +474,26 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { + bool inherited_update = false; + if (IS_DUMMY_REL(rel)) { /* We already proved the relation empty, so nothing more to do */ } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_pathlist(root, rel, rti, rte); + /* + * If it's a target relation, set the pathlists of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (root->inherited_update && root->parse->resultRelation == rti) + { + inherited_update = true; + set_inherit_target_rel_pathlists(root, rel, rti, rte); + } + else + set_append_rel_pathlist(root, rel, rti, rte); } else { @@ -541,8 +568,12 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, if (set_rel_pathlist_hook) (*set_rel_pathlist_hook) (root, rel, rti, rte); - /* Now find the cheapest of the paths for this rel */ - set_cheapest(rel); + /* + * Now find the cheapest of the paths for this rel, unless it's an + * inheritance parent and this is an update/delete operation. + */ + if (!inherited_update) + set_cheapest(rel); #ifdef OPTIMIZER_DEBUG debug_print_rel(root, rel); @@ -904,6 +935,382 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) } /* + * set_inherit_target_rel_sizes + * Set size estimates for the child target relations + * + * The passed-in rel represents the target relation of the query that is + * known to have inheritance children. This is very much like + * set_append_rel_size, except it doesn't set the size estimates for the + * passed-in rel itself, because we don't need to "append" the children + * in this case. + */ +static void +set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + bool has_live_children; + ListCell *l; + Relids live_children = NULL; + bool did_pruning = false; + + /* Guard against stack overflow due to overly deep inheritance tree. */ + check_stack_depth(); + + Assert(IS_SIMPLE_REL(rel)); + + /* + * If the partitioned relation has any baserestrictinfo quals then we + * attempt to use these quals to prune away partitions that cannot + * possibly contain any tuples matching these quals. In this case we'll + * store the relids of all partitions which could possibly contain a + * matching tuple, and skip anything else in the loop below. + */ + if (enable_partition_pruning && + rte->relkind == RELKIND_PARTITIONED_TABLE && + rel->baserestrictinfo != NIL) + { + live_children = prune_append_rel_partitions(rel); + did_pruning = true; + } + + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + List *childquals; + Index cq_min_security; + bool have_const_false_cq; + PlannerInfo *subroot; + ListCell *lc; + List *translated_exprs, + *child_target_exprs; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + + /* + * The child rel's RelOptInfo was already created during + * add_base_rels_to_query. + */ + childrel = find_base_rel(root, childRTindex); + Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = NIL; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, + (Node *) rinfo->clause, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + /* track minimum security level among child quals */ + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might have + * securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals, see expand_inherited_rtentry().) Pull any such + * securityQuals up into the baserestrictinfo for the child. This is + * similar to process_security_barrier_quals() for the parent rel, + * except that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* not likely that we'd see constants here, so no check */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, false, false, + security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* + * OK, we've got all the baserestrictinfo quals for this child. + */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) + { + /* This partition was pruned; skip it. */ + set_dummy_rel_pathlist(childrel); + continue; + } + + if (relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* This child is excluded; skip it. */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Add missing Vars to child's reltarget. + * + * add_inherit_target_child_root() would've added only those that are + * needed to be present in the top-level tlist (or ones that + * preprocess_targetlist thinks are needed to be in the tlist.) We + * may need other attributes such as those contained in WHERE clauses, + * which are already computed for the parent during + * deconstruct_jointree processing of the original query (child's + * query never goes through deconstruct_jointree.) + */ + translated_exprs = (List *) + adjust_appendrel_attrs(root, + (Node *) rel->reltarget->exprs, + 1, &appinfo); + child_target_exprs = childrel->reltarget->exprs; + foreach(lc, translated_exprs) + { + Expr *expr = lfirst(lc); + + if (!list_member(child_target_exprs, expr)) + child_target_exprs = lappend(child_target_exprs, expr); + } + + subroot = root->inh_target_child_roots[childRTindex]; + + /* + * Also, We have to find any ECs containing parent's expressions and + * *replace* them with their copies containing child expressions. + */ + if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) + { + /* + * First make copies for ECs for the child's root, because some + * might be modified to replace parent EC expressions by child + * expressions in add_child_rel_equivalences. + */ + subroot->eq_classes = NIL; + foreach(lc, root->eq_classes) + { + EquivalenceClass *ec = lfirst(lc); + EquivalenceClass *new_ec = makeNode(EquivalenceClass); + + /* + * First memcpy which creates shallow copies of all + * the members and then make copies of members that could + * change. + * + * XXX comment in _copyPathKey says it's OK to recycle EC + * pointers, but as long as we do the whole planning for a + * given child using a given root, copying ECs like this + * shouldn't be a problem. Maybe, the following code could + * be in _copyEquivalenceClass()? + */ + memcpy(new_ec, ec, sizeof(EquivalenceClass)); + new_ec->ec_opfamilies = list_copy(ec->ec_opfamilies); + new_ec->ec_members = list_copy(ec->ec_members); + new_ec->ec_sources = list_copy(ec->ec_sources); + new_ec->ec_derives = list_copy(ec->ec_derives); + new_ec->ec_relids = bms_copy(ec->ec_relids); + + subroot->eq_classes = lappend(subroot->eq_classes, new_ec); + } + + add_child_rel_equivalences(subroot, appinfo, rel, childrel, true); + } + childrel->has_eclass_joins = rel->has_eclass_joins; + + /* Translate join quals. */ + childrel->joininfo = (List *) + adjust_appendrel_attrs(subroot, + (Node *) rel->joininfo, + 1, &appinfo); + + /* + * If parallelism is allowable for this query in general, see whether + * it's allowable for this childrel in particular. For consistency, + * do this before calling set_rel_size() for the child. + */ + if (root->glob->parallelModeOK) + set_rel_consider_parallel(subroot, childrel, childRTE); + + /* + * Compute the child's size using possibly modified subroot. + */ + set_rel_size(subroot, childrel, childRTindex, childRTE); + + /* If the child itself is partitioned it may turn into a dummy rel. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* We have at least one live child. */ + has_live_children = true; + + Assert(childrel->rows > 0); + } + + if (has_live_children) + { + /* + * Set a non-zero value here to cope with the caller's requirement + * that non-dummy relations are actually not empty. We don't try to + * be accurate here, because we're not going to create a path that + * combines the children outputs. + */ + rel->rows = 1; + } + else + { + /* + * All children were excluded by constraints, so mark the relation + * ass dummy. We must do this in this phase so that the rel's + * dummy-ness is visible when we generate paths for other rels. + */ + set_dummy_rel_pathlist(rel); + } +} + +/* + * set_inherit_target_rel_pathlists + * Build access paths for the child target relations + * + * Similar to set_append_rel_pathlist, except that we build paths of the + * children, but don't build an Append path. + */ +static void +set_inherit_target_rel_pathlists(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + ListCell *l; + + /* Nothing to do if all the children were excluded. */ + if (IS_DUMMY_REL(rel)) + return; + + /* Generate access paths for each of the children of passed-in rel */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + PlannerInfo *subroot; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + /* Re-locate the child RTE and RelOptInfo */ + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + childrel = root->simple_rel_array[childRTindex]; + subroot = root->inh_target_child_roots[childRTindex]; + /* Transfer the value from main root to subroot. */ + subroot->total_table_pages = root->total_table_pages; + + /* + * If set_append_rel_size() decided the parent appendrel was + * parallel-unsafe at some point after visiting this child rel, we + * need to propagate the unsafety marking down to the child, so that + * we don't generate useless partial paths for it. + */ + if (!rel->consider_parallel) + childrel->consider_parallel = false; + + /* + * Compute the child's access paths. + */ + set_rel_pathlist(subroot, childrel, childRTindex, childRTE); + } +} + +/* * set_append_rel_size * Set size estimates for a simple "append relation" * @@ -1046,7 +1453,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, * EquivalenceClass data structures. */ if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) - add_child_rel_equivalences(root, appinfo, rel, childrel); + add_child_rel_equivalences(root, appinfo, rel, childrel, + false); childrel->has_eclass_joins = rel->has_eclass_joins; /* @@ -2625,6 +3033,140 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows) } /* + * inheritance_make_rel_from_joinlist + * Perform join planning for all non-dummy leaf inheritance children + * in their role as an UPDATE/DELETE query's target relation + * + * If a child relation is a partitioned table, its children are processed in + * turn by recursively calling this function. + */ +static void +inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; +#ifdef USE_ASSERT_CHECKING + Relids all_baserels; +#endif + + /* For UPDATE/DELETE queries, the top parent can only ever be a table. */ + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + Assert(planner_rt_fetch(resultRelation, root)->rtekind == RTE_RELATION); + + /* Nothing to do. */ + if (IS_DUMMY_REL(find_base_rel(root, resultRelation))) + return; + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + PlannerInfo *subroot; + RelOptInfo *childrel; + RelOptInfo *childjoinrel; + List *translated_joinlist; + + if (appinfo->parent_relid != resultRelation) + continue; + + childrel = find_base_rel(root, appinfo->child_relid); + + /* Ignore excluded/pruned children. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* Add this child. */ + root->inh_target_child_rels = lappend_int(root->inh_target_child_rels, + appinfo->child_relid); + + /* Perform join planning with child subroot. */ + subroot = root->inh_target_child_roots[appinfo->child_relid]; + Assert(subroot->parse->resultRelation > 0); + + /* + * Modify joinlist such that relations joined to the top parent rel + * appear to be joined to the child rel instead. Do the same for + * any SpecialJoinInfo structs. + */ + translated_joinlist = (List *) + adjust_appendrel_attrs(subroot, + (Node *) joinlist, + 1, &appinfo); + subroot->join_info_list = (List *) + adjust_appendrel_attrs(subroot, + (Node *) root->join_info_list, + 1, &appinfo); + + /* + * Sub-partitioned tables have to be processed recursively using the + * translated subroot as the parent, because AppendRelInfos link + * sub-partitions to their immediate parents, not the root partitioned + * table. + */ + if (childrel->part_scheme != NULL) + { + inheritance_make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * Add this child relation as a placeholder in the parent root's + * inh_target_child_joinrels so that inheritance_planner see same + * number of entries as inh_target_child_rels. + */ + root->inh_target_child_joinrels = + lappend(root->inh_target_child_joinrels, childrel); + + /* Also propagate this child's own children into parent's list. */ + if (subroot->inh_target_child_rels != NIL) + { + root->inh_target_child_rels = + list_concat(root->inh_target_child_rels, + subroot->inh_target_child_rels); + root->inh_target_child_joinrels = + list_concat(root->inh_target_child_joinrels, + subroot->inh_target_child_joinrels); + } + continue; + } + + /* + * Since we added the child rel directly into the join tree, we must + * modify it to be a "base" rel instead of an "other" rel, which the + * join planning code expects the relations being joined to be. + */ + childrel->reloptkind = RELOPT_BASEREL; + + Assert(subroot->join_rel_list == NIL); + Assert(subroot->join_rel_hash == NULL); + + /* Perform join planning and save the resulting RelOptInfo. */ + childjoinrel = make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * Remember this child target rel. inheritance_planner will perform + * the remaining steps of planning for each child relation separately. + * Specifically, it will call grouping_planner on every + * RelOptInfo contained in the inh_target_child_rels list, each of + * which represents the source of tuples to be modified for a given + * target child rel. + */ + root->inh_target_child_joinrels = + lappend(root->inh_target_child_joinrels, childjoinrel); +#ifdef USE_ASSERT_CHECKING + /* + * The following implements essentially the same Assert as in + * make_one_rel, our caller. + */ + all_baserels = bms_copy(root->all_baserels); + all_baserels = bms_del_member(all_baserels, + root->parse->resultRelation); + all_baserels = bms_add_member(all_baserels, + subroot->parse->resultRelation); + Assert(bms_equal(childjoinrel->relids, all_baserels)); +#endif + } +} + +/* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. * diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 6e134ae1d2..10136ef940 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -2104,12 +2104,16 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root, * * parent_rel and child_rel could be derived from appinfo, but since the * caller has already computed them, we might as well just pass them in. + * + * If 'replace' is true then the child EC members *replace* the corresponding + * parent members. */ void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, - RelOptInfo *child_rel) + RelOptInfo *child_rel, + bool replace) { ListCell *lc1; @@ -2117,14 +2121,23 @@ add_child_rel_equivalences(PlannerInfo *root, { EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc1); ListCell *lc2; + ListCell *prev; + ListCell *next; /* * If this EC contains a volatile expression, then generating child * EMs would be downright dangerous, so skip it. We rely on a - * volatile EC having only one EM. + * volatile EC having only one EM. If the caller asked to *replace* + * the original parent expression with the child one, then it's + * okay to proceed, because we wouldn't be adding another expression + * as being equivalent in that case. */ if (cur_ec->ec_has_volatile) - continue; + { + Assert(list_length(cur_ec->ec_members) == 1); + if (!replace) + continue; + } /* * No point in searching if parent rel not mentioned in eclass; but we @@ -2134,12 +2147,18 @@ add_child_rel_equivalences(PlannerInfo *root, !bms_is_subset(parent_rel->relids, cur_ec->ec_relids)) continue; - foreach(lc2, cur_ec->ec_members) + prev = NULL; + for (lc2 = list_head(cur_ec->ec_members); lc2 != NULL; lc2 = next) { EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2); + next = lnext(lc2); + if (cur_em->em_is_const) + { + prev = lc2; continue; /* ignore consts here */ + } /* Does it reference parent_rel? */ if (bms_overlap(cur_em->em_relids, parent_rel->relids)) @@ -2177,10 +2196,38 @@ add_child_rel_equivalences(PlannerInfo *root, child_rel->relids); } + /* + * Caller may have asked us to replace the parent expression + * with the child expression, so delete the parent expression + * first. + */ + if (replace) + cur_ec->ec_members = list_delete_cell(cur_ec->ec_members, + lc2, prev); + + /* + * If we're replacing, then the new member isn't really a + * a child, so em_is_child should be set to false. + */ (void) add_eq_member(cur_ec, child_expr, new_relids, new_nullable_relids, - true, cur_em->em_datatype); + !replace, cur_em->em_datatype); } + else + prev = lc2; + } + + if (replace) + { + /* + * Now fix up EC's relids set. It's OK to modify EC like this, + * because caller must have made a copy of the original EC. + * For example, see adjust_inherited_target_child_root. + */ + cur_ec->ec_relids = bms_difference(cur_ec->ec_relids, + parent_rel->relids); + cur_ec->ec_relids = bms_add_members(cur_ec->ec_relids, + child_rel->relids); } } } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 97d0c28132..1e1c7efef9 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -2019,12 +2019,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) /* * During setrefs.c, we'll need the grouping_map to fix up the cols lists * in GroupingFunc nodes. Save it for setrefs.c to use. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be grouping in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->grouping_map == NULL); root->grouping_map = grouping_map; @@ -2181,12 +2176,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) * with InitPlan output params. (We can't just do that locally in the * MinMaxAgg node, because path nodes above here may have Agg references * as well.) Save the mmaggregates list to tell setrefs.c to do that. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be aggregates in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->minmax_aggs == NIL); root->minmax_aggs = best_path->mmaggregates; diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index fc97a1bb50..78557aeecb 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -28,7 +28,7 @@ #include "optimizer/paths.h" #include "optimizer/placeholder.h" #include "optimizer/planmain.h" - +#include "optimizer/prep.h" /* * query_planner @@ -59,6 +59,7 @@ query_planner(PlannerInfo *root, List *tlist, Query *parse = root->parse; List *joinlist; RelOptInfo *final_rel; + Index rti; /* * If the query has an empty join tree, then it's something easy like @@ -232,13 +233,52 @@ query_planner(PlannerInfo *root, List *tlist, extract_restriction_or_clauses(root); /* + * Construct the all_baserels Relids set. + */ + root->all_baserels = NULL; + for (rti = 1; rti < root->simple_rel_array_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + Assert(brel->relid == rti); /* sanity check on array */ + + /* ignore RTEs that are "other rels" */ + if (brel->reloptkind != RELOPT_BASEREL) + continue; + + root->all_baserels = bms_add_member(root->all_baserels, brel->relid); + } + + /* + * Add child subroots needed to use during planning for individual child + * targets + */ + if (root->inherited_update) + { + root->inh_target_child_roots = (PlannerInfo **) + palloc0(root->simple_rel_array_size * + sizeof(PlannerInfo *)); + add_inherited_target_child_roots(root); + } + + /* * Ready to do the primary planning. */ final_rel = make_one_rel(root, joinlist); - /* Check that we got at least one usable path */ - if (!final_rel || !final_rel->cheapest_total_path || - final_rel->cheapest_total_path->param_info != NULL) + /* + * Check that we got at least one usable path. In the case of an + * inherited update/delete operation, no path has been created for + * the query's actual target relation yet. + */ + if (!root->inherited_update && + (!final_rel || + !final_rel->cheapest_total_path || + final_rel->cheapest_total_path->param_info != NULL)) elog(ERROR, "failed to construct the join relation"); return final_rel; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 5ba612922f..703babfdf0 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -39,6 +39,7 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "nodes/relation.h" #include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" @@ -131,7 +132,7 @@ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static void inheritance_planner(PlannerInfo *root); static void grouping_planner(PlannerInfo *root, bool inheritance_update, - double tuple_fraction); + RelOptInfo *planned_rel, double tuple_fraction); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, int *tleref_to_colnum_map); @@ -635,7 +636,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->grouping_map = NULL; root->minmax_aggs = NIL; root->qual_security_level = 0; - root->inhTargetKind = INHKIND_NONE; root->hasRecursion = hasRecursion; if (hasRecursion) root->wt_param_id = assign_special_exec_param(root); @@ -725,6 +725,13 @@ subquery_planner(PlannerGlobal *glob, Query *parse, expand_inherited_tables(root); /* + * Now that we have figured out "actual" inheritance situation of the + * relations, set whether the query is an inherited UPDATE/DELETE. + */ + root->inherited_update = (parse->resultRelation && + rt_fetch(parse->resultRelation, parse->rtable)->inh); + + /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -975,7 +982,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh) inheritance_planner(root); else - grouping_planner(root, false, tuple_fraction); + grouping_planner(root, false, NULL, tuple_fraction); /* * Capture the set of outer-level param IDs we have access to, for use in @@ -1151,13 +1158,41 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr) * inheritance set. * * We have to handle this case differently from cases where a source relation - * is an inheritance set. Source inheritance is expanded at the bottom of the - * plan tree (see allpaths.c), but target inheritance has to be expanded at - * the top. The reason is that for UPDATE, each target relation needs a - * different targetlist matching its own column set. Fortunately, + * is an inheritance set, where outputs of all the children are combined and + * combined output consumed using the source relation's (parent's) column set. + * For UPDATE, each target relation, where the query's output will go, needs + * a different targetlist matching its own column set. So, we will need to + * translate the query such that it produces the desired output required by + * each of the child target relations and plan each separately. Fortunately, * the UPDATE/DELETE target can never be the nullable side of an outer join, * so it's OK to generate the plan this way. * + * Planning steps that need to be performed separately for each child and + * any preliminary processing needed for that are handled by the following + * sub-routines of query_planner: + * + * add_inherit_target_roots - this creates copies of PlannerInfo for each + * child after query_planner has finished processing the join tree and + * creating EquivalenceClasses, so that working state of planning need + * not be created afresh for each child, especially the various arrays + * + * set_inherit_target_rel_sizes - this sets size estimates for child + * relations, replace the parent EC members by corresponding child ones + * in their respective subroots + * + * set_inherit_target_rel_pathlists - this creates Paths for scanning + * individual child relations + * + * inherit_make_rel_from_joinlist - this translates the jointree, replacing + * the target relation in the original jointree (the root parent) by + * individual child target relations and performs join planning on the + * resulting join tree, saving the RelOptInfos of resulting join relations + * into the top-level PlannerInfo + * + * Finally, here we apply grouping_planner to each child join relation + * produced by set_inherit_target_rel_pathlists to so that its path produces + * the desired targetlist. + * * Returns nothing; the useful output is in the Paths we attach to * the (UPPERREL_FINAL, NULL) upperrel stored in *root. * @@ -1169,14 +1204,8 @@ inheritance_planner(PlannerInfo *root) { Query *parse = root->parse; int top_parentRTindex = parse->resultRelation; - Bitmapset *subqueryRTindexes; - Bitmapset *modifiableARIindexes; int nominalRelation = -1; Index rootRelation = 0; - List *final_rtable = NIL; - int save_rel_array_size = 0; - RelOptInfo **save_rel_array = NULL; - AppendRelInfo **save_append_rel_array = NULL; List *subpaths = NIL; List *subroots = NIL; List *resultRelations = NIL; @@ -1184,70 +1213,47 @@ inheritance_planner(PlannerInfo *root) List *returningLists = NIL; List *rowMarks; RelOptInfo *final_rel; - ListCell *lc; - Index rti; + ListCell *lc1, + *lc2; RangeTblEntry *parent_rte; - PlannerInfo *parent_root; - Query *parent_parse; - Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); - PlannerInfo **parent_roots = NULL; + List *tlist; + standard_qp_extra qp_extra; + RelOptInfo *planned_rel; + /* Inheritance is never used for insert. */ Assert(parse->commandType != CMD_INSERT); /* - * We generate a modified instance of the original Query for each target - * relation, plan that, and put all the plans into a list that will be - * controlled by a single ModifyTable node. All the instances share the - * same rangetable, but each instance must have its own set of subquery - * RTEs within the finished rangetable because (1) they are likely to get - * scribbled on during planning, and (2) it's not inconceivable that - * subqueries could get planned differently in different cases. We need - * not create duplicate copies of other RTE kinds, in particular not the - * target relations, because they don't have either of those issues. Not - * having to duplicate the target relations is important because doing so - * (1) would result in a rangetable of length O(N^2) for N targets, with - * at least O(N^3) work expended here; and (2) would greatly complicate - * management of the rowMarks list. + * Let query_planner generate the access paths for the query for each + * target child relation. * - * To begin with, generate a bitmapset of the relids of the subquery RTEs. + * First, save the unexpanded version of the query's targetlist. + * create_inherit_target_child_root will use it as base when expanding + * it for a given child relation as the query's target relation instead + * of the parent. */ - subqueryRTindexes = NULL; - rti = 1; - foreach(lc, parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - if (rte->rtekind == RTE_SUBQUERY) - subqueryRTindexes = bms_add_member(subqueryRTindexes, rti); - rti++; - } + root->unexpanded_tlist = list_copy(root->parse->targetList); + tlist = preprocess_targetlist(root); + root->processed_tlist = tlist; + qp_extra.tlist = tlist; + qp_extra.activeWindows = NIL; + qp_extra.groupClause = NIL; + planned_rel = query_planner(root, tlist, standard_qp_callback, &qp_extra); /* - * Next, we want to identify which AppendRelInfo items contain references - * to any of the aforesaid subquery RTEs. These items will need to be - * copied and modified to adjust their subquery references; whereas the - * other ones need not be touched. It's worth being tense over this - * because we can usually avoid processing most of the AppendRelInfo - * items, thereby saving O(N^2) space and time when the target is a large - * inheritance tree. We can identify AppendRelInfo items by their - * child_relid, since that should be unique within the list. + * If it turned out during query planning that all the children are dummy + * (pruned or excluded by constraints), no need to do the steps below. + * Let grouping_planner finish up the final path. */ - modifiableARIindexes = NULL; - if (subqueryRTindexes != NULL) + if (IS_DUMMY_REL(planned_rel)) { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - - if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) || - bms_is_member(appinfo->child_relid, subqueryRTindexes) || - bms_overlap(pull_varnos((Node *) appinfo->translated_vars), - subqueryRTindexes)) - modifiableARIindexes = bms_add_member(modifiableARIindexes, - appinfo->child_relid); - } + grouping_planner(root, false, planned_rel, 0.0); + return; } + Assert(planned_rel->relid == top_parentRTindex); + Assert(planned_rel->reloptkind == RELOPT_BASEREL); + /* * If the parent RTE is a partitioned table, we should use that as the * nominal target relation, because the RTEs added for partitioned tables @@ -1255,7 +1261,7 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); + parent_rte = planner_rt_fetch(top_parentRTindex, root); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1263,95 +1269,39 @@ inheritance_planner(PlannerInfo *root) } /* - * The PlannerInfo for each child is obtained by translating the relevant - * members of the PlannerInfo for its immediate parent, which we find - * using the parent_relid in its AppendRelInfo. We save the PlannerInfo - * for each parent in an array indexed by relid for fast retrieval. Since - * the maximum number of parents is limited by the number of RTEs in the - * query, we use that number to allocate the array. An extra entry is - * needed since relids start from 1. + * Get on with finalizing the path for each child target relation by + * calling grouping_planner on its joinrel. */ - parent_roots = (PlannerInfo **) palloc0((list_length(parse->rtable) + 1) * - sizeof(PlannerInfo *)); - parent_roots[top_parentRTindex] = root; - - /* - * And now we can get on with generating a plan for each child table. - */ - foreach(lc, root->append_rel_list) + forboth(lc1, root->inh_target_child_rels, + lc2, root->inh_target_child_joinrels) { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - PlannerInfo *subroot; + Index childRTindex = lfirst_int(lc1); + RelOptInfo *childjoinrel = lfirst(lc2); + PlannerInfo *subroot = root->inh_target_child_roots[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; RangeTblEntry *child_rte; RelOptInfo *sub_final_rel; Path *subpath; - /* append_rel_list contains all append rels; ignore others */ - if (!bms_is_member(appinfo->parent_relid, parent_relids)) - continue; - - /* - * expand_inherited_rtentry() always processes a parent before any of - * that parent's children, so the parent_root for this relation should - * already be available. - */ - parent_root = parent_roots[appinfo->parent_relid]; - Assert(parent_root != NULL); - parent_parse = parent_root->parse; - - /* - * We need a working copy of the PlannerInfo so that we can control - * propagation of information back to the main copy. - */ - subroot = makeNode(PlannerInfo); - memcpy(subroot, parent_root, sizeof(PlannerInfo)); - - /* - * Generate modified query with this rel as target. We first apply - * adjust_appendrel_attrs, which copies the Query and changes - * references to the parent RTE to refer to the current child RTE, - * then fool around with subquery RTEs. - */ - subroot->parse = (Query *) - adjust_appendrel_attrs(parent_root, - (Node *) parent_parse, - 1, &appinfo); + Assert(subroot != NULL); + Assert(subroot->parse->resultRelation == childRTindex); /* * If there are securityQuals attached to the parent, move them to the * child rel (they've already been transformed properly for that). */ - parent_rte = rt_fetch(appinfo->parent_relid, subroot->parse->rtable); - child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable); + parent_rte = planner_rt_fetch(appinfo->parent_relid, subroot); + child_rte = planner_rt_fetch(appinfo->child_relid, subroot); child_rte->securityQuals = parent_rte->securityQuals; parent_rte->securityQuals = NIL; /* - * Mark whether we're planning a query to a partitioned table or an - * inheritance parent. + * Ignore a partitioned child. Instead, the paths of its children will + * be added to subpaths. */ - subroot->inhTargetKind = - (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED; - - /* - * If this child is further partitioned, remember it as a parent. - * Since a partitioned table does not have any data, we don't need to - * create a plan for it, and we can stop processing it here. We do, - * however, need to remember its modified PlannerInfo for use when - * processing its children, since we'll update their varnos based on - * the delta from immediate parent to child, not from top to child. - * - * Note: a very non-obvious point is that we have not yet added - * duplicate subquery RTEs to the subroot's rtable. We mustn't, - * because then its children would have two sets of duplicates, - * confusing matters. - */ - if (child_rte->inh) + if (childjoinrel->part_scheme) { - Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE); - parent_relids = bms_add_member(parent_relids, appinfo->child_relid); - parent_roots[appinfo->child_relid] = subroot; - + Assert(child_rte->inh); continue; } @@ -1379,111 +1329,8 @@ inheritance_planner(PlannerInfo *root) if (nominalRelation < 0) nominalRelation = appinfo->child_relid; - /* - * The rowMarks list might contain references to subquery RTEs, so - * make a copy that we can apply ChangeVarNodes to. (Fortunately, the - * executor doesn't need to see the modified copies --- we can just - * pass it the original rowMarks list.) - */ - subroot->rowMarks = copyObject(parent_root->rowMarks); - - /* - * The append_rel_list likewise might contain references to subquery - * RTEs (if any subqueries were flattenable UNION ALLs). So prepare - * to apply ChangeVarNodes to that, too. As explained above, we only - * want to copy items that actually contain such references; the rest - * can just get linked into the subroot's append_rel_list. - * - * If we know there are no such references, we can just use the outer - * append_rel_list unmodified. - */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - subroot->append_rel_list = NIL; - foreach(lc2, parent_root->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, modifiableARIindexes)) - appinfo2 = copyObject(appinfo2); - - subroot->append_rel_list = lappend(subroot->append_rel_list, - appinfo2); - } - } - - /* - * Add placeholders to the child Query's rangetable list to fill the - * RT indexes already reserved for subqueries in previous children. - * These won't be referenced, so there's no need to make them very - * valid-looking. - */ - while (list_length(subroot->parse->rtable) < list_length(final_rtable)) - subroot->parse->rtable = lappend(subroot->parse->rtable, - makeNode(RangeTblEntry)); - - /* - * If this isn't the first child Query, generate duplicates of all - * subquery RTEs, and adjust Var numbering to reference the - * duplicates. To simplify the loop logic, we scan the original rtable - * not the copy just made by adjust_appendrel_attrs; that should be OK - * since subquery RTEs couldn't contain any references to the target - * rel. - */ - if (final_rtable != NIL && subqueryRTindexes != NULL) - { - ListCell *lr; - - rti = 1; - foreach(lr, parent_parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lr); - - if (bms_is_member(rti, subqueryRTindexes)) - { - Index newrti; - - /* - * The RTE can't contain any references to its own RT - * index, except in its securityQuals, so we can save a - * few cycles by applying ChangeVarNodes to the rest of - * the rangetable before we append the RTE to it. - */ - newrti = list_length(subroot->parse->rtable) + 1; - ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); - ChangeVarNodes((Node *) subroot->rowMarks, rti, newrti, 0); - /* Skip processing unchanging parts of append_rel_list */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - foreach(lc2, subroot->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, - modifiableARIindexes)) - ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); - } - } - rte = copyObject(rte); - ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); - subroot->parse->rtable = lappend(subroot->parse->rtable, - rte); - } - rti++; - } - } - - /* There shouldn't be any OJ info to translate, as yet */ - Assert(subroot->join_info_list == NIL); - /* and we haven't created PlaceHolderInfos, either */ - Assert(subroot->placeholder_list == NIL); - - /* Generate Path(s) for accessing this result relation */ - grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ ); + /* Apply the top-level targetlist to childjoinrel's path. */ + grouping_planner(subroot, true, childjoinrel, 0.0); /* * Select cheapest path in case there's more than one. We always run @@ -1495,45 +1342,10 @@ inheritance_planner(PlannerInfo *root) subpath = sub_final_rel->cheapest_total_path; /* - * If this child rel was excluded by constraint exclusion, exclude it - * from the result plan. + * child rel cannot be empty, or inheritance_make_rel_from_joinlist + * wouldn't have put it in the list to begin with. */ - if (IS_DUMMY_PATH(subpath)) - continue; - - /* - * If this is the first non-excluded child, its post-planning rtable - * becomes the initial contents of final_rtable; otherwise, append - * just its modified subquery RTEs to final_rtable. - */ - if (final_rtable == NIL) - final_rtable = subroot->parse->rtable; - else - final_rtable = list_concat(final_rtable, - list_copy_tail(subroot->parse->rtable, - list_length(final_rtable))); - - /* - * We need to collect all the RelOptInfos from all child plans into - * the main PlannerInfo, since setrefs.c will need them. We use the - * last child's simple_rel_array (previous ones are too short), so we - * have to propagate forward the RelOptInfos that were already built - * in previous children. - */ - Assert(subroot->simple_rel_array_size >= save_rel_array_size); - for (rti = 1; rti < save_rel_array_size; rti++) - { - RelOptInfo *brel = save_rel_array[rti]; - - if (brel) - subroot->simple_rel_array[rti] = brel; - } - save_rel_array_size = subroot->simple_rel_array_size; - save_rel_array = subroot->simple_rel_array; - save_append_rel_array = subroot->append_rel_array; - - /* Make sure any initplans from this rel get into the outer list */ - root->init_plans = subroot->init_plans; + Assert(!IS_DUMMY_PATH(subpath)); /* Build list of sub-paths */ subpaths = lappend(subpaths, subpath); @@ -1565,36 +1377,6 @@ inheritance_planner(PlannerInfo *root) */ /* - * If we managed to exclude every child rel, return a dummy plan; it - * doesn't even need a ModifyTable node. - */ - if (subpaths == NIL) - { - set_dummy_rel_pathlist(final_rel); - return; - } - - /* - * Put back the final adjusted rtable into the master copy of the Query. - * (We mustn't do this if we found no non-excluded children.) - */ - parse->rtable = final_rtable; - root->simple_rel_array_size = save_rel_array_size; - root->simple_rel_array = save_rel_array; - root->append_rel_array = save_append_rel_array; - - /* Must reconstruct master's simple_rte_array, too */ - root->simple_rte_array = (RangeTblEntry **) - palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *)); - rti = 1; - foreach(lc, final_rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - root->simple_rte_array[rti++] = rte; - } - - /* * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will * have dealt with fetching non-locked marked rows, else we need to have * ModifyTable do that. @@ -1634,6 +1416,12 @@ inheritance_planner(PlannerInfo *root) * (inheritance_planner will create a single ModifyTable node covering all the * target tables.) * + * If non-NULL, planned_rel is a RelOptInfo containing paths for the query's + * top-level joinrel, which the caller produced by itself. In that case, this + * function only needs to adjust the targetlist of its cheapest_total_path. + * The only caller that may pass such a RelOptInfo currently is + * inheritance_planner. + * * tuple_fraction is the fraction of tuples we expect will be retrieved. * tuple_fraction is interpreted as follows: * 0: expect all tuples to be retrieved (normal case) @@ -1652,6 +1440,7 @@ inheritance_planner(PlannerInfo *root) */ static void grouping_planner(PlannerInfo *root, bool inheritance_update, + RelOptInfo *planned_rel, double tuple_fraction) { Query *parse = root->parse; @@ -1664,7 +1453,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, List *final_targets; List *final_targets_contain_srfs; bool final_target_parallel_safe; - RelOptInfo *current_rel; + RelOptInfo *current_rel = planned_rel; RelOptInfo *final_rel; ListCell *lc; @@ -1704,6 +1493,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * special work for recursive unions is the responsibility of * plan_set_operations. */ + Assert(current_rel == NULL); current_rel = plan_set_operations(root); /* @@ -1793,17 +1583,26 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, parse->groupClause = preprocess_groupclause(root, NIL); } - /* Preprocess targetlist */ - tlist = preprocess_targetlist(root); - /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation so - * that we can transfer its decoration (resnames etc) to the topmost - * tlist of the finished Plan. + * Preprocess targetlist, if needed. If the caller has already done + * query planning, root->processed_tlist already contains the desired + * targetlist. */ - root->processed_tlist = tlist; + if (planned_rel == NULL) + { + tlist = preprocess_targetlist(root); + + /* + * We are now done hacking up the query's targetlist. Most of the + * remaining planning work will be done with the PathTarget + * representation of tlists, but save aside the full representation + * so that we can transfer its decoration (resnames etc) to the + * topmost tlist of the finished Plan. + */ + root->processed_tlist = tlist; + } + else + tlist = root->processed_tlist; /* * Collect statistics about aggregates for estimating costs, and mark @@ -1883,8 +1682,9 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * We also generate (in standard_qp_callback) pathkey representations * of the query's sort clause, distinct clause, etc. */ - current_rel = query_planner(root, tlist, - standard_qp_callback, &qp_extra); + if (current_rel == NULL) + current_rel = query_planner(root, tlist, + standard_qp_callback, &qp_extra); /* * Convert the query's result tlist into PathTarget format. diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 77dbf4eba3..87adfa02b1 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -917,7 +917,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->grouping_map = NULL; subroot->minmax_aggs = NIL; subroot->qual_security_level = 0; - subroot->inhTargetKind = INHKIND_NONE; subroot->hasRecursion = false; subroot->wt_param_id = -1; subroot->non_recursive_path = NULL; diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index 37fbc3a117..0b9ca39a51 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -394,8 +394,39 @@ adjust_appendrel_attrs_mutator(Node *node, context->appinfos); return (Node *) phv; } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index db474acbc5..0d2b42f72e 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -21,11 +21,14 @@ #include "miscadmin.h" #include "optimizer/appendinfo.h" #include "optimizer/inherit.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "utils/rel.h" +static PlannerInfo *create_inherited_target_child_root(PlannerInfo *root, + AppendRelInfo *appinfo); static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti); static void expand_partitioned_rtentry(PlannerInfo *root, @@ -492,3 +495,109 @@ translate_col_privs(const Bitmapset *parent_privs, return child_privs; } + +/* + * add_inherited_target_child_roots + * For each child of of the query's result relation, this translates the + * original query to match the child and creates a PlannerInfo containing + * the translated query + * + * Child PlannerInfo reuses most of the parent PlannerInfo's fields unchanged, + * except unexpanded_tlist, processed_tlist, and all_baserels, all of which + * are based on the child relation. + */ +void +add_inherited_target_child_roots(PlannerInfo *root) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; + + Assert(root->inh_target_child_roots != NULL); + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + RangeTblEntry *childRTE; + PlannerInfo *subroot; + + if (appinfo->parent_relid != resultRelation) + continue; + + /* + * Create a PlannerInfo for processing this child target relation + * with. + */ + subroot = create_inherited_target_child_root(root, appinfo); + root->inh_target_child_roots[appinfo->child_relid] = subroot; + + /* + * If the child is a partitioned table, recurse to do this for its + * partitions. + */ + childRTE = root->simple_rte_array[appinfo->child_relid]; + if (childRTE->inh) + add_inherited_target_child_roots(subroot); + } +} + +/* + * create_inherited_target_child_root + * Workhorse of add_inherited_target_child_roots + */ +static PlannerInfo * +create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) +{ + PlannerInfo *subroot; + List *tlist; + + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + + /* + * Translate the original query to replace Vars of the parent table + * by the corresponding Vars of the child table and to make child the main + * target relation of the query. + */ + subroot = makeNode(PlannerInfo); + memcpy(subroot, root, sizeof(PlannerInfo)); + + /* + * Restore the original, unexpanded targetlist, that is, the one before + * preprocess_targetlist was run on the original query. We'll run + * preprocess_targetlist after translating the query and the targetlist, + * so that it is expanded according to child's tuple descriptor. + */ + root->parse->targetList = root->unexpanded_tlist; + subroot->parse = (Query *) adjust_appendrel_attrs(root, + (Node *) root->parse, + 1, &appinfo); + + /* Save the just translated targetlist as unexpanded_tlist in the child's + * subroot, so that this child's own children can use it. Must use copy + * because subroot->parse->targetList will be modified soon. + */ + subroot->unexpanded_tlist = list_copy(subroot->parse->targetList); + + /* + * Apply planner's expansion of targetlist, such as adding various junk + * column, filling placeholder entries for dropped columns, etc., all of + * which occurs with the child's TupleDesc. + */ + tlist = preprocess_targetlist(subroot); + subroot->processed_tlist = tlist; + + /* Add any newly added Vars to the child RelOptInfo. */ + build_base_rel_tlists(subroot, tlist); + + /* + * Adjust all_baserels to replace the original target relation with the + * child target relation. Copy it before modifying though. + */ + subroot->all_baserels = bms_copy(root->all_baserels); + subroot->all_baserels = bms_del_member(subroot->all_baserels, + root->parse->resultRelation); + subroot->all_baserels = bms_add_member(subroot->all_baserels, + subroot->parse->resultRelation); + + return subroot; +} diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 48ffc5f254..6009de309e 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1264,36 +1264,6 @@ get_relation_constraints(PlannerInfo *root, } } - /* - * Append partition predicates, if any. - * - * For selects, partition pruning uses the parent table's partition bound - * descriptor, instead of constraint exclusion which is driven by the - * individual partition's partition constraint. - */ - if (enable_partition_pruning && root->parse->commandType != CMD_SELECT) - { - List *pcqual = RelationGetPartitionQual(relation); - - if (pcqual) - { - /* - * Run the partition quals through const-simplification similar to - * check constraints. We skip canonicalize_qual, though, because - * partition quals should be in canonical form already; also, - * since the qual is in implicit-AND format, we'd have to - * explicitly convert it to explicit-AND format and back again. - */ - pcqual = (List *) eval_const_expressions(root, (Node *) pcqual); - - /* Fix Vars to have the desired varno */ - if (varno != 1) - ChangeVarNodes((Node *) pcqual, 1, varno, 0); - - result = list_concat(result, pcqual); - } - } - heap_close(relation, NoLock); return result; @@ -1417,40 +1387,15 @@ relation_excluded_by_constraints(PlannerInfo *root, /* * Skip further tests, depending on constraint_exclusion. */ - switch (constraint_exclusion) - { - case CONSTRAINT_EXCLUSION_OFF: - - /* - * Don't prune if feature turned off -- except if the relation is - * a partition. While partprune.c-style partition pruning is not - * yet in use for all cases (update/delete is not handled), it - * would be a UI horror to use different user-visible controls - * depending on such a volatile implementation detail. Therefore, - * for partitioned tables we use enable_partition_pruning to - * control this behavior. - */ - if (root->inhTargetKind == INHKIND_PARTITIONED) - break; - return false; - - case CONSTRAINT_EXCLUSION_PARTITION: - - /* - * When constraint_exclusion is set to 'partition' we only handle - * OTHER_MEMBER_RELs, or BASERELs in cases where the result target - * is an inheritance parent or a partitioned table. - */ - if ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) && - !(rel->reloptkind == RELOPT_BASEREL && - root->inhTargetKind != INHKIND_NONE && - rel->relid == root->parse->resultRelation)) - return false; - break; - - case CONSTRAINT_EXCLUSION_ON: - break; /* always try to exclude */ - } + if (constraint_exclusion == CONSTRAINT_EXCLUSION_OFF) + return false; + /* + * When constraint_exclusion is set to 'partition' we only handle + * OTHER_MEMBER_RELs. + */ + else if (constraint_exclusion == CONSTRAINT_EXCLUSION_PARTITION && + rel->reloptkind != RELOPT_OTHER_MEMBER_REL) + return false; /* * Check for self-contradictory restriction clauses. We dare not make diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 3430061361..beb2e323a8 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -319,9 +319,6 @@ typedef struct PlannerInfo Index qual_security_level; /* minimum security_level for quals */ /* Note: qual_security_level is zero if there are no securityQuals */ - InheritanceKind inhTargetKind; /* indicates if the target relation is an - * inheritance child or partition or a - * partitioned table */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */ bool hasDeletedRTEs; /* true if any RTE was deleted from jointree */ @@ -343,6 +340,40 @@ typedef struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* + * The following fields are set during query planning portion of an + * inherited UPDATE/DEELETE operation. + */ + bool inherited_update; /* UPDATE/DELETE on inheritance parent? */ + + /* + * This stores the original version of the query's targetlist that's + * not modified by the planner. + */ + List *unexpanded_tlist; + + /* + * Array containing simple_rel_array_size elements, indexed by rangetable + * index (entry 0 is wasted like simple_rel_array). Only elements + * corresponding to individual inheritance child target relations are + * non-NULL. Content of each PlannerInfo is same as the parent + * PlannerInfo, except for the parse tree which is a translated copy of + * the parent's parse tree. + */ + struct PlannerInfo **inh_target_child_roots; + + /* List of RT indexes of child RT indexes. */ + List *inh_target_child_rels; + + /* + * RelOptInfos corresponding to each child target rel. For leaf children, + * it's the RelOptInfo representing the output of make_rel_from_joinlist() + * called with the parent rel in the original join tree replaced by a + * given leaf child. For non-leaf children, it's the baserel RelOptInfo + * itself, left as a placeholder. + */ + List *inh_target_child_joinrels; } PlannerInfo; diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h index b2687abd4e..1dbc2ca5bf 100644 --- a/src/include/optimizer/inherit.h +++ b/src/include/optimizer/inherit.h @@ -18,5 +18,6 @@ extern void expand_inherited_tables(PlannerInfo *root); +extern void add_inherited_target_child_roots(PlannerInfo *root); #endif /* INHERIT_H */ diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 666217c189..f524ab6beb 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -161,7 +161,8 @@ extern EquivalenceClass *match_eclasses_to_foreign_key_col(PlannerInfo *root, extern void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, - RelOptInfo *child_rel); + RelOptInfo *child_rel, + bool replace); extern List *generate_implied_equalities_for_column(PlannerInfo *root, RelOptInfo *rel, ec_matches_callback_type callback, diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index c55de5d476..c9f02c1fb0 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1780,7 +1780,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_1 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_1 -> Nested Loop Semi Join @@ -1788,7 +1788,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_2 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_2 (28 rows) -- 2.11.0
v14-0003-Store-inheritance-root-parent-index-in-otherrel-.patch (text/plain, 3.3K) download From abdaaca3ce8ec87809440e0650cbdaf25b4bbc99 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 26 Oct 2018 16:45:59 +0900 Subject: [PATCH v14 3/6] Store inheritance root parent index in otherrel's RelOptInfo Although it's set by build_simple_rel, it's not being used by any code yet. --- src/backend/nodes/outfuncs.c | 1 + src/backend/optimizer/util/relnode.c | 15 +++++++++++++-- src/include/nodes/relation.h | 9 +++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 158da68925..67697b8884 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2272,6 +2272,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_BOOL_FIELD(consider_partitionwise_join); WRITE_BITMAPSET_FIELD(top_parent_relids); WRITE_NODE_FIELD(partitioned_child_rels); + WRITE_UINT_FIELD(inh_root_parent); } static void diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index fe83ec4519..45f9950935 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -195,6 +195,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->joininfo = NIL; rel->has_eclass_joins = false; rel->consider_partitionwise_join = false; /* might get changed later */ + rel->top_parent_relids = NULL; /* might be changed later */ rel->part_scheme = NULL; rel->nparts = 0; rel->boundinfo = NULL; @@ -203,6 +204,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->partexprs = NULL; rel->nullable_partexprs = NULL; rel->partitioned_child_rels = NIL; + rel->inh_root_parent = 0; /* might be changed later */ /* * Pass top parent's relids down the inheritance hierarchy. If the parent @@ -216,9 +218,18 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->top_parent_relids = parent->top_parent_relids; else rel->top_parent_relids = bms_copy(parent->relids); + + /* + * For inheritance child relations, we also set inh_root_parent. + * Note that 'parent' might itself be a child (a sub-partitioned + * partition), in which case we simply use its value of + * inh_root_parent. + */ + if (parent->rtekind == RTE_RELATION) + rel->inh_root_parent = parent->inh_root_parent > 0 ? + parent->inh_root_parent : + parent->relid; } - else - rel->top_parent_relids = NULL; /* Check type of rtable entry */ switch (rte->rtekind) diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index beb2e323a8..db439bd904 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -734,6 +734,15 @@ typedef struct RelOptInfo List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ + + /* + * For inheritance children, this is the RT index of inheritance table + * mentioned in the query from which this relation originated. + * top_parent_relids cannot be used for this, because if the inheritance + * root table is itself under UNION ALL, top_parent_relids contains the + * RT index of UNION ALL parent subquery. + */ + Index inh_root_parent; } RelOptInfo; /* -- 2.11.0
v14-0004-Lazy-creation-of-RTEs-for-inheritance-children.patch (text/plain, 95.3K) download From 0525aff10c5752dc4ee661bc538562cb313e8979 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 18:14:13 +0900 Subject: [PATCH v14 4/6] Lazy creation of RTEs for inheritance children Currently, expand_inherited_tables called from subquery_planner opens all inheritance child tables and adds them to the query's range table and PlannerInfo in the form of AppendRelInfo's initially and later RelOptInfo's. For partitioning, that's pretty wasteful, because it's possible to determine only the partitions that will need to be scanned using partition pruning, which doesn't require opening the partitions themselves. However, the point at which the above processing happens is too early to perform partition pruning. This commit rearranges things within the planner so that inheritance child relations can be added to the Query and PlannerInfo during make_one_rel, just before set_base_rel_sizes is called. Although the late initialization approach only benefits the partitioning case, it seems undesirable to do it only for partitioned tables, because it means adding code to handle partitions specially at various places within the planner. So, *all* inheritance parent tables are expanded in make_one_rel. All unpruned partitions are added to Query in the form of their RangeTblEntry's being added to the range table and to PlannerInfo in the form of an AppendRelInfo and a RelOptInfo for each. For partitioning, although we don't create a RangeTblEntry and RelOptInfo for pruned partitions at make_one_rel time, partitionwise join code relies on the fact that even though partitions may have been pruned, they'd still own a RelOptInfo to handle the outer join case where the pruned partition appears on the nullable side of join. Partitionwise join code deals with that by allocating dummy RelOptInfos for pruned partitions that are based mostly on their parent's properties. There are some regression test diffs: 1. Caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child, as seen in the partition_aggregate.out test output. 2. Those in postgres_fdw.out are caused by the fact that junk columns required for row marking are added to reltarget->exprs later than user columns, because the row marking junk columns arent added until after the inheritance is expanded which as of this commit is later than it used to be. --- contrib/postgres_fdw/expected/postgres_fdw.out | 32 +- src/backend/optimizer/path/allpaths.c | 372 ++-------- src/backend/optimizer/path/joinrels.c | 61 ++ src/backend/optimizer/plan/initsplan.c | 58 -- src/backend/optimizer/plan/planmain.c | 10 + src/backend/optimizer/plan/planner.c | 74 +- src/backend/optimizer/plan/setrefs.c | 6 + src/backend/optimizer/prep/preptlist.c | 15 +- src/backend/optimizer/prep/prepunion.c | 3 + src/backend/optimizer/util/appendinfo.c | 45 +- src/backend/optimizer/util/inherit.c | 817 +++++++++++++++------- src/backend/optimizer/util/plancat.c | 47 +- src/backend/optimizer/util/relnode.c | 84 +-- src/backend/partitioning/partprune.c | 43 +- src/include/nodes/relation.h | 9 + src/include/optimizer/appendinfo.h | 7 +- src/include/optimizer/pathnode.h | 1 + src/include/optimizer/plancat.h | 4 +- src/include/optimizer/prep.h | 3 +- src/include/partitioning/partprune.h | 2 +- src/test/regress/expected/partition_aggregate.out | 4 +- 21 files changed, 913 insertions(+), 784 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index bb92d9d37a..33a33606ef 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7072,15 +7072,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7110,15 +7110,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7147,15 +7147,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid @@ -7165,15 +7165,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (39 rows) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 3324d485e4..042d6b2b0d 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -45,7 +45,6 @@ #include "optimizer/var.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -383,6 +382,14 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, else if (rte->inh) { /* + * expand_inherited_tables may have proved that the relation is empty. + * For example, if it's a partitioned table with 0 partitions or all + * of its partitions are pruned. In that case nothing to do here. + */ + if (IS_DUMMY_REL(rel)) + return; + + /* * If it's a target relation, set the sizes of children instead. * Otherwise, we'll append the outputs of children, so process it as * an "append relation". @@ -951,38 +958,18 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, int parentRTindex = rti; bool has_live_children; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); Assert(IS_SIMPLE_REL(rel)); - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - foreach(l, root->append_rel_list) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; PlannerInfo *subroot; ListCell *lc; List *translated_exprs, @@ -994,141 +981,22 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); + Assert(childrel != NULL); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals. */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); + if (IS_DUMMY_REL(childrel)) continue; - } - - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } if (relation_excluded_by_constraints(root, childrel, childRTE)) { @@ -1181,15 +1049,9 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, EquivalenceClass *new_ec = makeNode(EquivalenceClass); /* - * First memcpy which creates shallow copies of all - * the members and then make copies of members that could - * change. - * - * XXX comment in _copyPathKey says it's OK to recycle EC - * pointers, but as long as we do the whole planning for a - * given child using a given root, copying ECs like this - * shouldn't be a problem. Maybe, the following code could - * be in _copyEquivalenceClass()? + * First memcpy the original EC, which creates shallow copies + * of all the members and then make copies of members that + * might change during add_child_rel_equivalences(). */ memcpy(new_ec, ec, sizeof(EquivalenceClass)); new_ec->ec_opfamilies = list_copy(ec->ec_opfamilies); @@ -1332,8 +1194,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -1341,32 +1201,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Assert(IS_SIMPLE_REL(rel)); /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, we will bubble up - * the indexes of partitioned relations that appear down in the tree, so - * that when we've created Paths for all the children, the root - * partitioned table's list will contain all such indexes. - */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - rel->partitioned_child_rels = list_make1_int(rti); - - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - - /* * If this is a partitioned baserel, set the consider_partitionwise_join * flag; currently, we only consider partitionwise joins with the baserel * if its targetlist doesn't contain a whole-row Var. @@ -1403,12 +1237,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; ListCell *parentvars; ListCell *childvars; - ListCell *lc; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1416,18 +1246,35 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); + Assert(childrel != NULL); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); /* - * Copy/Modify targetlist. Even if this child is deemed empty, we need - * its targetlist in case it falls on nullable side in a child-join - * because of partitionwise join. + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals. + */ + if (IS_DUMMY_REL(childrel)) + continue; + + if (relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* + * This child need not be scanned, so we can omit it from the + * appendrel. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Copy/Modify targetlist. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -1457,144 +1304,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, false); childrel->has_eclass_joins = rel->has_eclass_joins; - /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* - * This child need not be scanned, so we can omit it from the - * appendrel. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - /* CE failed, so finish copying/modifying join quals. */ childrel->joininfo = (List *) adjust_appendrel_attrs(root, @@ -3070,6 +2779,7 @@ inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) continue; childrel = find_base_rel(root, appinfo->child_relid); + Assert(childrel != NULL); /* Ignore excluded/pruned children. */ if (IS_DUMMY_REL(childrel)) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index db18feccfe..411cdbd84f 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -20,6 +20,7 @@ #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" +#include "optimizer/tlist.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -49,6 +50,9 @@ static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, static SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, Relids left_relids, Relids right_relids); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + int partidx); /* @@ -1379,6 +1383,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (IS_SIMPLE_REL(rel1) && child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts); + if (IS_SIMPLE_REL(rel1) && child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts); + /* We should never try to join two overlapping sets of rels. */ Assert(!bms_overlap(child_rel1->relids, child_rel2->relids)); child_joinrelids = bms_union(child_rel1->relids, child_rel2->relids); @@ -1627,3 +1636,55 @@ build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo, return sjinfo; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +static RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx) +{ + RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + AppendRelInfo *appinfo = make_append_rel_info(parent, parentrte, + parent->tupdesc, + parentrte->relid, + parent->reltype, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index a66374094f..c628f3d9e7 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -616,64 +616,6 @@ create_lateral_join_info(PlannerInfo *root) bms_add_member(brel2->lateral_referencers, rti); } } - - /* - * Lastly, propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - */ - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - RangeTblEntry *brte = root->simple_rte_array[rti]; - - /* - * Skip empty slots. Also skip non-simple relations i.e. dead - * relations. - */ - if (brel == NULL || !IS_SIMPLE_REL(brel)) - continue; - - /* - * In the case of table inheritance, the parent RTE is directly linked - * to every child table via an AppendRelInfo. In the case of table - * partitioning, the inheritance hierarchy is expanded one level at a - * time rather than flattened. Therefore, an other member rel that is - * a partitioned table may have children of its own, and must - * therefore be marked with the appropriate lateral info so that those - * children eventually get marked also. - */ - Assert(brte); - if (brel->reloptkind == RELOPT_OTHER_MEMBER_REL && - (brte->rtekind != RTE_RELATION || - brte->relkind != RELKIND_PARTITIONED_TABLE)) - continue; - - if (brte->inh) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); - RelOptInfo *childrel; - - if (appinfo->parent_relid != rti) - continue; - childrel = root->simple_rel_array[appinfo->child_relid]; - Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - Assert(childrel->direct_lateral_relids == NULL); - childrel->direct_lateral_relids = brel->direct_lateral_relids; - Assert(childrel->lateral_relids == NULL); - childrel->lateral_relids = brel->lateral_relids; - Assert(childrel->lateral_referencers == NULL); - childrel->lateral_referencers = brel->lateral_referencers; - } - } - } } diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 78557aeecb..62e11157da 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -254,6 +254,16 @@ query_planner(PlannerInfo *root, List *tlist, } /* + * Expand RT entries that represent inherited or partitioned tables. + * This will perform partition pruning on partitioned tables in the + * original range table and also recursively on any child partitioned + * tables that were added by the expansion of the original parent(s). + * As new entries are added to the range table, various arrays in the + * PlannerInfo will be expanded accordingly. + */ + expand_inherited_tables(root); + + /* * Add child subroots needed to use during planning for individual child * targets */ diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 703babfdf0..f80f35eed3 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -25,6 +25,7 @@ #include "access/sysattr.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -704,27 +705,18 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } /* - * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). - */ - preprocess_rowmarks(root); - - /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain base relations not joins, so it's OK (and marginally more - * efficient) to do it after checking for join RTEs. We must do it after - * pulling up subqueries, else we'd fail to handle inherited tables in - * subqueries. - */ - expand_inherited_tables(root); - - /* * Now that we have figured out "actual" inheritance situation of the * relations, set whether the query is an inherited UPDATE/DELETE. */ @@ -732,6 +724,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh); /* + * Preprocess RowMark information. We need to do this after subquery + * pullup (so that all non-inherited RTEs are present). + */ + preprocess_rowmarks(root); + + /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -1233,7 +1231,9 @@ inheritance_planner(PlannerInfo *root) * of the parent. */ root->unexpanded_tlist = list_copy(root->parse->targetList); - tlist = preprocess_targetlist(root); + + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root, false); root->processed_tlist = tlist; qp_extra.tlist = tlist; qp_extra.activeWindows = NIL; @@ -1243,9 +1243,11 @@ inheritance_planner(PlannerInfo *root) /* * If it turned out during query planning that all the children are dummy * (pruned or excluded by constraints), no need to do the steps below. - * Let grouping_planner finish up the final path. + * For regular inheritance case, it's possible that we only need to modify + * the parent table. Let grouping_planner finish up the final path. */ - if (IS_DUMMY_REL(planned_rel)) + parent_rte = planner_rt_fetch(top_parentRTindex, root); + if (!parent_rte->inh || IS_DUMMY_REL(planned_rel)) { grouping_planner(root, false, planned_rel, 0.0); return; @@ -1261,7 +1263,6 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = planner_rt_fetch(top_parentRTindex, root); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1590,14 +1591,19 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, */ if (planned_rel == NULL) { - tlist = preprocess_targetlist(root); + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root, false); /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation - * so that we can transfer its decoration (resnames etc) to the - * topmost tlist of the finished Plan. + * We are now mostly done hacking up the query's targetlist. More + * columns might be added during query_planner after inheritance + * expansion in make_one_rel, because some children may require + * different column than the parent for row mark handling; see + * add_rowmark_junk_columns(). Most of the remaining planning + * work will be done with the PathTarget representation of tlists, + * but save aside the full representation so that we can transfer + * its decoration (resnames etc) to the topmost tlist of the + * finished Plan. */ root->processed_tlist = tlist; } @@ -2373,7 +2379,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2398,7 +2404,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } @@ -6835,6 +6841,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -6936,6 +6946,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 5d363edab8..4dcdede6b6 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -325,6 +325,12 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) UPPERREL_FINAL, NULL))) add_rtes_to_flat_rtable(rel->subroot, true); } + /* + * A NULL rel also means an unplanned subquery rte, so apply + * flatten_unplanned_rtes. + */ + else + flatten_unplanned_rtes(glob, rte); } rti++; } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index f8bc2dd257..a55949c305 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -65,9 +65,13 @@ static List *expand_targetlist(List *tlist, int command_type, * * As a side effect, if there's an ON CONFLICT UPDATE clause, its targetlist * is also preprocessed (and updated in-place). + * + * inheritance_expanded specifies whether inheritance tables are expanded + * as of calling this function. Caller must specify it because that affects + * which row marking related junk columns get added to the targetlist. */ List * -preprocess_targetlist(PlannerInfo *root) +preprocess_targetlist(PlannerInfo *root, bool inheritance_expanded) { Query *parse = root->parse; int result_relation = parse->resultRelation; @@ -134,6 +138,15 @@ preprocess_targetlist(PlannerInfo *root) if (rc->rti != rc->prti) continue; + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which will change the parent row + * mark's allMarkTypes fields. + */ + if (rc->isParent && !inheritance_expanded) + continue; + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) { /* Need to fetch TID */ diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 55eeb5127c..d4d36248a1 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -31,13 +31,16 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" +#include "optimizer/var.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" #include "utils/lsyscache.h" diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index 0b9ca39a51..1500ea22b9 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -31,10 +31,10 @@ typedef struct AppendRelInfo **appinfos; } adjust_appendrel_attrs_context; -static void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, adjust_appendrel_attrs_context *context); static List *adjust_inherited_tlist(List *tlist, @@ -46,18 +46,20 @@ static List *adjust_inherited_tlist(List *tlist, * Build an AppendRelInfo for the parent-child pair */ AppendRelInfo * -make_append_rel_info(Relation parentrel, Relation childrel, - Index parentRTindex, Index childRTindex) +make_append_rel_info(RelOptInfo *parent, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childtype, + Index childRTindex) { AppendRelInfo *appinfo = makeNode(AppendRelInfo); - appinfo->parent_relid = parentRTindex; + appinfo->parent_relid = parent->relid; appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->rd_rel->reltype; - appinfo->child_reltype = childrel->rd_rel->reltype; - make_inh_translation_list(parentrel, childrel, childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = RelationGetRelid(parentrel); + appinfo->parent_reltype = parent->reltype; + appinfo->child_reltype = childtype; + make_inh_translation_list(parent->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; return appinfo; } @@ -70,14 +72,11 @@ make_append_rel_info(Relation parentrel, Relation childrel, * For paranoia's sake, we match type/collation as well as attribute name. */ static void -make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, - List **translated_vars) +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) { List *vars = NIL; - TupleDesc old_tupdesc = RelationGetDescr(oldrelation); - TupleDesc new_tupdesc = RelationGetDescr(newrelation); - Oid new_relid = RelationGetRelid(newrelation); int oldnatts = old_tupdesc->natts; int newnatts = new_tupdesc->natts; int old_attno; @@ -107,7 +106,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, * When we are generating the "translation list" for the parent table * of an inheritance set, no need to search for matches. */ - if (oldrelation == newrelation) + if (from_rel == to_rel) { vars = lappend(vars, makeVar(newvarno, (AttrNumber) (old_attno + 1), @@ -133,10 +132,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, { HeapTuple newtup; - newtup = SearchSysCacheAttName(new_relid, attname); + newtup = SearchSysCacheAttName(to_rel, attname); if (!newtup) elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; ReleaseSysCache(newtup); @@ -146,10 +145,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, /* Found it, check type and collation match */ if (atttypid != att->atttypid || atttypmod != att->atttypmod) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); if (attcollation != att->attcollation) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); vars = lappend(vars, makeVar(newvarno, (AttrNumber) (new_attno + 1), diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 0d2b42f72e..0e927254c7 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -18,33 +18,43 @@ #include "access/sysattr.h" #include "catalog/partition.h" #include "catalog/pg_inherits.h" +#include "catalog/pg_type.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" #include "optimizer/inherit.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" +#include "optimizer/var.h" +#include "partitioning/partprune.h" #include "utils/rel.h" static PlannerInfo *create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo); -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); +static void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti); +static void expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, + RangeTblEntry *rte, Index rti, RelOptInfo *rel); static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); - +static RelOptInfo *build_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); /* * expand_inherited_tables @@ -52,37 +62,145 @@ static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, * into an "append relation". At the conclusion of this process, * the "inh" flag is set in all and only those RTEs that are append * relation parents. + * + * Note that although we're calling the combined output of inheritance set + * relations an "append relation" here, the caller may not always want to + * combine the relations. For example, if the parent of the inheritance + * set is the query's target relation, each child relation is processed + * on its own as the query's target relation. */ void expand_inherited_tables(PlannerInfo *root) { - Index nrtes; + int orig_rtable_size; Index rti; - ListCell *rl; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; /* * expand_inherited_rtentry may add RTEs to parse->rtable. The function is * expected to recursively handle any RTEs that it creates with inh=true. * So just scan as far as the original end of the rtable list. */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) + for (rti = 1; rti < orig_rtable_size; rti++) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + if (rte->inh) + expand_inherited_rtentry(root, brel, rte, rti); } } /* * expand_inherited_rtentry - * Check whether a rangetable entry represents an inheritance set. - * If so, add entries for all the child tables to the query's - * rangetable, and build AppendRelInfo nodes for all the child tables - * and add them to root->append_rel_list. If not, clear the entry's - * "inh" flag to prevent later code from looking for AppendRelInfos. + * This initializes RelOptInfos for inheritance child relations if the + * passed-in relation has any + * + * 'rel' is the parent relation, whose range table entry ('rte') has been + * marked to require adding children. Parent could either be a subquery (if + * we flattened UNION ALL query) or a table that's known to have (or once had) + * inheritance children. The latter consists of both regular inheritance + * parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as a child to be scanned as part of the + * inheritance set. The original RTE is considered to represent the whole + * inheritance set. + */ +static void +expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_inheritance_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_nonpartitioned_inherited_rtentry(root, rte, rti, rel); + } +} + +/* + * expand_nonpartitioned_inherited_rtentry + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. * * Note that the original RTE is considered to represent the whole * inheritance set. The first of the generated RTEs is an RTE for the same @@ -93,55 +211,35 @@ expand_inherited_tables(PlannerInfo *root) * regular inheritance, a parent RTE must always have at least two associated * AppendRelInfos: one corresponding to the parent table as a simple member of * inheritance set and one or more corresponding to the actual children. - * Since a partitioned table is not scanned, it might have only one associated - * AppendRelInfo. */ static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, + RangeTblEntry *rte, + Index rti, + RelOptInfo *rel) { Oid parentOID; PlanRowMark *oldrc; - Relation oldrelation; - LOCKMODE lockmode; + LOCKMODE lockmode = rte->rellockmode; List *inhOIDs; ListCell *l; + int num_children; + int num_children_added = 0; - /* Does RT entry allow inheritance? */ - if (!rte->inh) - return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } - /* Fast path for common case of childless table */ + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); parentOID = rte->relid; - if (!has_subclass(parentOID)) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * The rewriter should already have obtained an appropriate lock on each - * relation named in the query. However, for each child relation we add - * to the query, we must obtain an appropriate lock, because this will be - * the first use of those relations in the parse/rewrite/plan pipeline. - * Child rels should use the same lockmode as their parent. - */ - lockmode = rte->rellockmode; /* Scan for all members of inheritance set, acquire needed locks */ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); /* * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite above has_subclass() check, if table - * once had a child but no longer does. + * case. This could happen despite has_subclass() check performed by + * subquery_planner, if table once had a child but no longer does. */ - if (list_length(inhOIDs) < 2) + num_children = list_length(inhOIDs); + if (num_children < 2) { /* Clear flag before returning */ rte->inh = false; @@ -149,216 +247,219 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } /* - * If parent relation is selected FOR UPDATE/SHARE, we need to mark its - * PlanRowMark as isParent = true, and generate a new PlanRowMark for each - * child. + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. */ oldrc = get_plan_rowmark(root->rowMarks, rti); - if (oldrc) - oldrc->isParent = true; + Assert(oldrc == NULL || oldrc->isParent); /* - * Must open the parent relation to examine its tupdesc. We need not lock - * it; we assume the rewriter already did. + * Must expand PlannerInfo arrays by num_children before we can add + * children. */ - oldrelation = heap_open(parentOID, NoLock); + expand_planner_arrays(root, num_children); - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) + foreach(l, inhOIDs) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); - - /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. - */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); - } - else - { - List *appinfos = NIL; + Oid childOID = lfirst_oid(l); + Relation newrelation; RangeTblEntry *childrte; Index childRTindex; + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. */ - foreach(l, inhOIDs) + if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = heap_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - heap_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - heap_close(newrelation, NoLock); + heap_close(newrelation, lockmode); + continue; } - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); + (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation, &childrte, + &childRTindex); + Assert(childrte != NULL); + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + Assert(childRTindex > 0); + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; } - heap_close(oldrelation, NoLock); + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } } /* * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) + Index parentRTindex, RelOptInfo *parentrel) { + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; - check_stack_depth(); - - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + expand_planner_arrays(root, bms_num_members(partindexes)); /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. */ - if (partdesc->nparts == 0) - { - parentrte->inh = false; - return; - } + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); - for (i = 0; i < partdesc->nparts; i++) + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) { - Oid childOID = partdesc->oids[i]; - Relation childrel; + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + RangeTblEntry *childrte; + Index childRTindex; - /* Open rel; we already have required locks */ - childrel = heap_open(childOID, NoLock); + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation, + &childrte, &childRTindex); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + + /* If the child is partitioned itself, expand it too. */ + if (childrel->part_scheme) + { + Assert(childrte->inh); expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); + childrel); + } + } - /* Close child relation, but keep locks */ - heap_close(childrel, NoLock); + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); } } /* - * expand_single_inheritance_child - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) + * The return value is the RelOptInfo that's added. * * PlanRowMarks still carry the top-parent's RTI, and the top-parent's * allMarkTypes field still accumulates values from all descendents. * * "parentrte" and "parentRTindex" are immediate parent's RTE and * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. */ -static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); Oid childOID = RelationGetRelid(childrel); RangeTblEntry *childrte; Index childRTindex; AppendRelInfo *appinfo; + RelOptInfo *childrelopt; /* * Build an RTE for the child, and attach to query's rangetable list. We * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh = false. Also, set requiredPerms to zero since - * all required permissions checks are done on the original RTE. Likewise, - * set the child's securityQuals to empty, because we only want to apply - * the parent's RLS conditions regardless of what RLS properties + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties * individual children may have. (This is an intentional choice to make * inherited RLS work like regular permissions checks.) The parent * securityQuals will be propagated to children along with other base @@ -368,49 +469,42 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childrte_p = childrte; childrte->relid = childOID; childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) - childrte->inh = true; - else - childrte->inh = false; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); childrte->requiredPerms = 0; childrte->securityQuals = NIL; parse->rtable = lappend(parse->rtable, childrte); childRTindex = list_length(parse->rtable); *childRTindex_p = childRTindex; - /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. - */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) - { - appinfo = make_append_rel_info(parentrel, childrel, - parentRTindex, childRTindex); - *appinfos = lappend(*appinfos, appinfo); + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + RelationGetDescr(childrel), + RelationGetRelid(childrel), + RelationGetForm(childrel)->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); } /* @@ -442,6 +536,253 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need to create one, + * because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_inheritance_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; +} + +/* + * build_inheritance_child_rel + * Build a RelOptInfo for child relation of an inheritance set + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields based on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turn out to be self-contradictory, + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + List *childquals; + ListCell *lc; + bool have_const_false_cq; + Index cq_min_security; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = false; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, parent->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual = (Node *) rinfo->clause; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, childqual, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might + * have securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals.) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except + * that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* + * not likely that we'd see constants here, so no + * check + */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, + false, + false, + security_level, + NULL, NULL, + NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* Set child's version of baserestrictinfo. */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; } /* @@ -583,7 +924,7 @@ create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) * column, filling placeholder entries for dropped columns, etc., all of * which occurs with the child's TupleDesc. */ - tlist = preprocess_targetlist(subroot); + tlist = preprocess_targetlist(subroot, true); subroot->processed_tlist = tlist; /* Add any newly added Vars to the child RelOptInfo. */ diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 6009de309e..293f9ac619 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -105,20 +105,20 @@ static void set_baserel_partition_key_exprs(Relation relation, * important for it. */ void -get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) +get_relation_info(PlannerInfo *root, RangeTblEntry *rte, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; bool hasindex; List *indexinfos = NIL; + bool inhparent = rte->inh; /* * We need not lock the relation since it was already locked, either by * the rewriter or when expand_inherited_rtentry() added it to the query's * rangetable. */ - relation = heap_open(relationObjectId, NoLock); + relation = heap_open(rte->relid, NoLock); /* Temporary and unlogged relations are inaccessible during recovery. */ if (!RelationNeedsWAL(relation) && RecoveryInProgress()) @@ -444,11 +444,32 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, get_relation_foreign_keys(root, rel, relation, inhparent); /* - * Collect info about relation's partitioning scheme, if any. Only - * inheritance parents may be partitioned. + * Collect some additional information for inheritance parents. */ - if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - set_relation_partition_info(root, rel, relation); + if (inhparent) + { + /* + * We'll need the TupleDesc when initializing the child relation. + * A copy is being made because concurrent changes might drop + * the relcache entry. That's possible because ALTER TABLE + * child_table NO INHERIT parent_table only requires an + * AccessShareLock on parent_table. + */ + rel->tupdesc = CreateTupleDescCopy(RelationGetDescr(relation)); + rel->reltype = RelationGetForm(relation)->reltype; + + /* + * If partitioned, also save the information of partitioning scheme, + * and whether the query updates any of the partition key columns. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + set_relation_partition_info(root, rel, relation); + root->partColsUpdated |= has_partition_attrs(relation, + rte->updatedCols, + NULL); + } + } heap_close(relation, NoLock); @@ -458,7 +479,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * removing an index, or adding a hypothetical index to the indexlist. */ if (get_relation_info_hook) - (*get_relation_info_hook) (root, relationObjectId, inhparent, rel); + (*get_relation_info_hook) (root, rte->relid, rte->inh, rel); } /* @@ -1844,16 +1865,20 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; + + /* + * Since we must've taken a lock on the table, it's okay to simply copy + * the pointers to relcache data here. + */ + rel->part_oids = partdesc->oids; + rel->boundinfo = partdesc->boundinfo; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 45f9950935..905fdc1419 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -131,6 +131,42 @@ setup_append_rel_array(PlannerInfo *root) } } +/* Expand the PlannerInfo arrays by add_size members and zero-init it. */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size = root->simple_rel_array_size + add_size; + + /* Expand various arrays and 0-initialize added bytes. */ + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + + if (root->append_rel_array) + { + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + } + else + { + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + } + + root->simple_rel_array_size = new_size; +} + /* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. @@ -236,7 +272,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte, rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -277,52 +313,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); - /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. - */ - if (rte->inh) - { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); - - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; - } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); - } - return rel; } diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 901433c68c..9bb472fd98 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -46,6 +46,7 @@ #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" #include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/planner.h" #include "optimizer/predtest.h" @@ -437,17 +438,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -541,23 +548,20 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ @@ -565,6 +569,13 @@ prune_append_rel_partitions(RelOptInfo *rel) return NULL; /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. */ @@ -591,15 +602,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index db439bd904..01a664d246 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -15,6 +15,7 @@ #define RELATION_H #include "access/sdir.h" +#include "access/tupdesc.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/params.h" @@ -729,6 +730,7 @@ typedef struct RelOptInfo int nparts; /* number of partitions */ struct PartitionBoundInfoData *boundinfo; /* Partition bounds */ List *partition_qual; /* partition constraint */ + Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ List **partexprs; /* Non-nullable partition key expressions. */ @@ -743,6 +745,13 @@ typedef struct RelOptInfo * RT index of UNION ALL parent subquery. */ Index inh_root_parent; + + /* + * Set only if this is an inheritance parent relation. This information + * is needed when initializing the planning info for children. + */ + TupleDesc tupdesc; /* A "copy" of the table's tuple desriptor */ + Oid reltype; /* Table's reltype */ } RelOptInfo; /* diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h index 156a0e077d..689d39517a 100644 --- a/src/include/optimizer/appendinfo.h +++ b/src/include/optimizer/appendinfo.h @@ -17,9 +17,10 @@ #include "nodes/relation.h" #include "utils/relcache.h" -extern AppendRelInfo *make_append_rel_info(Relation parentrel, - Relation childrel, - Index parentRTindex, Index childRTindex); +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parent, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childtype, + Index childRTindex); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index bd905d3328..e7d0a20c54 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -261,6 +261,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index a1b23251a1..d8091cdfa4 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -25,8 +25,8 @@ typedef void (*get_relation_info_hook_type) (PlannerInfo *root, extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; -extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); +extern void get_relation_info(PlannerInfo *root, RangeTblEntry *rte, + RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 62d45dd142..a329e87fc2 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -38,7 +38,8 @@ extern Expr *canonicalize_qual(Expr *qual, bool is_check); /* * prototypes for preptlist.c */ -extern List *preprocess_targetlist(PlannerInfo *root); +extern List *preprocess_targetlist(PlannerInfo *root, + bool inheritance_expanded); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h index ce9bc8d9fd..5c5d27d040 100644 --- a/src/include/partitioning/partprune.h +++ b/src/include/partitioning/partprune.h @@ -76,7 +76,7 @@ extern PartitionPruneInfo *make_partition_pruneinfo(PlannerInfo *root, List *subpaths, List *partitioned_rels, List *prunequal); -extern Relids prune_append_rel_partitions(RelOptInfo *rel); +extern Bitmapset *prune_append_rel_partitions(RelOptInfo *rel); extern Bitmapset *get_matching_partitions(PartitionPruneContext *context, List *pruning_steps); diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 6bc106831e..1450cef057 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) -- 2.11.0
v14-0005-Teach-planner-to-only-process-unpruned-partition.patch (text/plain, 6.9K) download From e40ac4001b12b5a04a905ae7b34d1e7c049c137d Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 15 Oct 2018 10:59:24 +0900 Subject: [PATCH v14 5/6] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/inherit.c | 1 + src/backend/optimizer/util/relnode.c | 3 +++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/relation.h | 4 ++++ 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 411cdbd84f..eb0e918d05 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1426,6 +1426,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index f80f35eed3..faf5f343fb 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6833,7 +6833,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -6841,9 +6843,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -6924,7 +6924,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -6936,7 +6935,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -6946,9 +6947,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 0e927254c7..776d02c81a 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -360,6 +360,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, /* Perform pruning. */ partindexes = prune_append_rel_partitions(parentrel); + parentrel->live_parts = partindexes; /* Must expand PlannerInfo arrays before we can add children. */ expand_planner_arrays(root, bms_num_members(partindexes)); diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 905fdc1419..fa52aaa6de 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1749,6 +1749,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 9bb472fd98..088b191bdd 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -441,29 +441,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 01a664d246..d27611c2c7 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -733,6 +733,10 @@ typedef struct RelOptInfo Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Indexes into part_rels of the non-NULL + * RelOptInfos of unpruned partitions; exists + * to avoid having to iterate over the entire + * part_rels array to filter NULL entries. */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.11.0
v14-0006-Do-not-lock-all-partitions-at-the-beginning.patch (text/plain, 1.7K) download From 9fff3f615968d365fc96ffcd2debbbf590bd6386 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 12 Oct 2018 19:12:53 +0900 Subject: [PATCH v14 6/6] Do not lock all partitions at the beginning --- src/backend/optimizer/util/inherit.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 776d02c81a..d88b9a7cb7 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -327,10 +327,6 @@ expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, * * Partitions are added to the query in order in which they are found in * the parent's PartitionDesc. - * - * Note: even though only the unpruned partitions will be added to the - * resulting plan, this still locks *all* partitions via find_all_inheritors - * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, @@ -344,10 +340,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, parentrel->inh_root_parent : parentRTindex; - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - /* * Initialize partitioned_child_rels to contain this RT index. * @@ -383,8 +375,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, RangeTblEntry *childrte; Index childRTindex; - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); + newrelation = heap_open(childOID, lockmode); Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-16 19:32  Alvaro Herrera <alvherre@2ndquadrant.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 1 reply; 265+ messages in thread

From: Alvaro Herrera @ 2019-01-16 19:32 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Amit Langote <amitlangote09@gmail.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers

On 2019-Jan-11, Amit Langote wrote:

> Looking around a bit more, I started thinking even build_child_join_sjinfo
> doesn't belong in appendinfo.c (just to be clear, it was defined in
> prepunion.c before this commit), so maybe we should move it to joinrels.c
> and instead export adjust_child_relids that's required by it from
> appendinfo.c.  There's already adjust_child_relids_multilevel in
> appendinfo.h, so having adjust_child_relids next to it isn't too bad.  At
> least not as bad as appendinfo.c exporting build_child_join_sjinfo for
> joinrels.c to use.

OK, pushed, thanks.  I may have caused merge conflicts with the rest of
the series, because I reordered some functions -- sorry about that.

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-17 02:17  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Alvaro Herrera <alvherre@2ndquadrant.com>
  0 siblings, 1 reply; 265+ messages in thread

From: Amit Langote @ 2019-01-17 02:17 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Amit Langote <amitlangote09@gmail.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers

On 2019/01/17 4:32, Alvaro Herrera wrote:
> On 2019-Jan-11, Amit Langote wrote:
> 
>> Looking around a bit more, I started thinking even build_child_join_sjinfo
>> doesn't belong in appendinfo.c (just to be clear, it was defined in
>> prepunion.c before this commit), so maybe we should move it to joinrels.c
>> and instead export adjust_child_relids that's required by it from
>> appendinfo.c.  There's already adjust_child_relids_multilevel in
>> appendinfo.h, so having adjust_child_relids next to it isn't too bad.  At
>> least not as bad as appendinfo.c exporting build_child_join_sjinfo for
>> joinrels.c to use.
> 
> OK, pushed, thanks.

Thank you.

> I may have caused merge conflicts with the rest of
> the series, because I reordered some functions -- sorry about that.

No problem.

I have rebased the patches.  Other than rebasing, I've squashed the patch
to add inh_root_parent field to RelOptInfo which contained no other code
changes to use that field into the patch that contains changes to start
using it.  Updated the commit message of lazy-partition-initialization
patch to be accurate (it contained old details that have changed since I
first wrote that commit message).

Thanks,
Amit

Attachments: download all (4 files, .tar.gz)

v15-0001-Overhaul-inheritance-update-delete-planning.patch (text/plain, 75.0K) download From 198dc6d3e932ff8aaa4d787368ee9fd7101459a1 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 11:18:12 +0900 Subject: [PATCH v15 1/4] Overhaul inheritance update/delete planning With the current method, inheritance_planner, which handles the planning for update/delete commands targeting inheritance trees, repeatedly calls query_planner for each child table. That's inefficient for two reasons: 1. partprune.c cannot be used to perform partition pruning, because it can only be invoked from query_planner. With the current approach, query_planner only sees the individual partitions in the query tree it receives, not the parent partitioned table. This leaves each partition to be pruned using constraint exclusion. (constraint exclusion cannon prune hash partitions which means UPDATE/DELETE don't support hash partition pruning.) 2. Repeated invocation of query_planner results in allocating large amounts of memory, especially if there are many child tables. Also, it involves repeatedly performing the same processing, such as jointree processing. This commit addresses both of the issues by modifying query_planner to handle the inheritance target case directly. Instead of having the caller create separate PlannerInfo's for individual child target relations and pass them to query_planner (actually, via grouping_planner), this teaches query_planner itself to create the child PlannerInfo's by the original parent PlannerInfo. It creates them *after* it has generated enough state in the latter, such as RelOptInfos for various relations, EquivalenceClasses after processing the join tree, and *before* generating the scan and join paths. Scan paths are generated normally, except that Paths of target children are not "appended". Join paths are generated separately for each child target relation, that is, by replacing the parent relation references in the original join tree with the child relation references. Joinrels (RelOptInfos thereof) corresponding to various child target relation are saved in the parent PlannerInfo for further processing. inheritance_planner now calls query_planner just once at the beginning with the original unmodified query, which takes care of generating top-level join rels for the child queries as described above. For each child join rel, it then calls grouping_planner using previously created child PlannerInfo to finish up the paths such that they produce query's top-level target list expanded according to a given child relation's descriptor. grouping_planner()'s interface is modified so that we can pass it what's called a 'planned_rel', a RelOptInfo that already contains the necessary paths needed to produce its output. This removes some existing code in inheritance_planner that dealt with any subquery RTEs in the query. The rationale of that code was that the subquery RTEs may change during each iteration of planning (that is, for different children), so different iterations better use different copies of those RTEs. That was handled by making fresh copies of those RTEs for each iteration of planning which were appended to the range table, accompanied by modifying all expressions and auxiliary structures that referenced the original subquery RTEs to instead reference the copies (that is, change the varnos). This copying meant we would end up adding S * N new entries to the original range table by the time we got to the last unpruned child, where S is the number of subquery RTEs in the original query and N the number of unpruned children. Since with the new code we perform planning just once, I think we don't need this special handling. Actually, there is a regression test output change due to no longer having copies of subquery RTEs (see the diff of partition_join.out file.) Also we no longer need the hack in relation_excluded_by_constraints() involving setting PlannerInfo.inhTargetKind to enable constraint exclusion for target child relation, which is no longer needed. Constraint exclusion now runs during query_planner step described above. Regression test output change in partition_join.out is due to the fact that we no longer create duplicates of subquery RTEs in child range tables. --- doc/src/sgml/ddl.sgml | 17 +- src/backend/nodes/outfuncs.c | 2 +- src/backend/optimizer/path/allpaths.c | 614 +++++++++++++++++++++++++-- src/backend/optimizer/path/equivclass.c | 57 ++- src/backend/optimizer/plan/createplan.c | 10 - src/backend/optimizer/plan/planmain.c | 48 ++- src/backend/optimizer/plan/planner.c | 444 ++++++------------- src/backend/optimizer/prep/prepjointree.c | 1 - src/backend/optimizer/util/appendinfo.c | 33 +- src/backend/optimizer/util/inherit.c | 109 +++++ src/backend/optimizer/util/plancat.c | 73 +--- src/include/nodes/relation.h | 37 +- src/include/optimizer/inherit.h | 1 + src/include/optimizer/paths.h | 3 +- src/test/regress/expected/partition_join.out | 4 +- 15 files changed, 988 insertions(+), 465 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 85e4358988..1bb5ca6b08 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -4439,24 +4439,11 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <note> <para> - Currently, pruning of partitions during the planning of an - <command>UPDATE</command> or <command>DELETE</command> command is - implemented using the constraint exclusion method (however, it is - controlled by the <literal>enable_partition_pruning</literal> rather than - <literal>constraint_exclusion</literal>) &mdash; see the following section - for details and caveats that apply. - </para> - - <para> Execution-time partition pruning currently only occurs for the <literal>Append</literal> and <literal>MergeAppend</literal> node types. It is not yet implemented for the <literal>ModifyTable</literal> node - type. - </para> - - <para> - Both of these behaviors are likely to be changed in a future release - of <productname>PostgreSQL</productname>. + type, but that is likely to be changed in a future release of + <productname>PostgreSQL</productname>. </para> </note> </sect2> diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 0fde876c77..158da68925 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2210,7 +2210,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); WRITE_UINT_FIELD(qual_security_level); - WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind); WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasDeletedRTEs); @@ -2221,6 +2220,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_BITMAPSET_FIELD(curOuterRels); WRITE_NODE_FIELD(curOuterParams); WRITE_BOOL_FIELD(partColsUpdated); + WRITE_BOOL_FIELD(inherited_update); } static void diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index bc389b52e4..3324d485e4 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -38,6 +38,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" @@ -92,8 +93,12 @@ static void set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); +static void set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte); static void set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); +static void set_inherit_target_rel_pathlists(PlannerInfo *root, + RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, @@ -120,6 +125,8 @@ static void set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); +static void inheritance_make_rel_from_joinlist(PlannerInfo *root, + List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, pushdown_safety_info *safetyInfo); static bool recurse_pushdown_safe(Node *setOp, Query *topquery, @@ -150,27 +157,6 @@ make_one_rel(PlannerInfo *root, List *joinlist) Index rti; double total_pages; - /* - * Construct the all_baserels Relids set. - */ - root->all_baserels = NULL; - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - - /* there may be empty slots corresponding to non-baserel RTEs */ - if (brel == NULL) - continue; - - Assert(brel->relid == rti); /* sanity check on array */ - - /* ignore RTEs that are "other rels" */ - if (brel->reloptkind != RELOPT_BASEREL) - continue; - - root->all_baserels = bms_add_member(root->all_baserels, brel->relid); - } - /* Mark base rels as to whether we care about fast-start plans */ set_base_rel_consider_startup(root); @@ -218,13 +204,34 @@ make_one_rel(PlannerInfo *root, List *joinlist) /* * Generate access paths for the entire join tree. + * + * For UPDATE/DELETE on an inheritance parent, join paths should be + * generated for each child result rel separately. */ - rel = make_rel_from_joinlist(root, joinlist); + if (root->parse->resultRelation && + root->simple_rte_array[root->parse->resultRelation]->inh) + { + inheritance_make_rel_from_joinlist(root, joinlist); - /* - * The result should join all and only the query's base rels. - */ - Assert(bms_equal(rel->relids, root->all_baserels)); + /* + * Return the RelOptInfo of original target relation, although this + * doesn't really contain the final path. inheritance_planner + * from where we got here will generate the final path, but it will + * do so by iterative over child subroots, not through this + * RelOptInfo. + */ + rel = find_base_rel(root, root->parse->resultRelation); + } + else + { + rel = make_rel_from_joinlist(root, joinlist); + + /* + * The result should join all and only the query's base rels. + */ + Assert(bms_equal(rel->relids, root->all_baserels)); + + } return rel; } @@ -308,9 +315,10 @@ set_base_rel_sizes(PlannerInfo *root) * If parallelism is allowable for this query in general, see whether * it's allowable for this rel in particular. We have to do this * before set_rel_size(), because (a) if this rel is an inheritance - * parent, set_append_rel_size() will use and perhaps change the rel's - * consider_parallel flag, and (b) for some RTE types, set_rel_size() - * goes ahead and makes paths immediately. + * parent, set_append_rel_size() or set_inherit_target_rel_sizes() + * will use and perhaps change the rel's consider_parallel flag, and + * (b) for some RTE types, set_rel_size() goes ahead and makes paths + * immediately. */ if (root->glob->parallelModeOK) set_rel_consider_parallel(root, rel, rte); @@ -374,8 +382,15 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_size(root, rel, rti, rte); + /* + * If it's a target relation, set the sizes of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (rti == root->parse->resultRelation) + set_inherit_target_rel_sizes(root, rel, rti, rte); + else + set_append_rel_size(root, rel, rti, rte); } else { @@ -459,14 +474,26 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { + bool inherited_update = false; + if (IS_DUMMY_REL(rel)) { /* We already proved the relation empty, so nothing more to do */ } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_pathlist(root, rel, rti, rte); + /* + * If it's a target relation, set the pathlists of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (root->inherited_update && root->parse->resultRelation == rti) + { + inherited_update = true; + set_inherit_target_rel_pathlists(root, rel, rti, rte); + } + else + set_append_rel_pathlist(root, rel, rti, rte); } else { @@ -541,8 +568,12 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, if (set_rel_pathlist_hook) (*set_rel_pathlist_hook) (root, rel, rti, rte); - /* Now find the cheapest of the paths for this rel */ - set_cheapest(rel); + /* + * Now find the cheapest of the paths for this rel, unless it's an + * inheritance parent and this is an update/delete operation. + */ + if (!inherited_update) + set_cheapest(rel); #ifdef OPTIMIZER_DEBUG debug_print_rel(root, rel); @@ -904,6 +935,382 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) } /* + * set_inherit_target_rel_sizes + * Set size estimates for the child target relations + * + * The passed-in rel represents the target relation of the query that is + * known to have inheritance children. This is very much like + * set_append_rel_size, except it doesn't set the size estimates for the + * passed-in rel itself, because we don't need to "append" the children + * in this case. + */ +static void +set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + bool has_live_children; + ListCell *l; + Relids live_children = NULL; + bool did_pruning = false; + + /* Guard against stack overflow due to overly deep inheritance tree. */ + check_stack_depth(); + + Assert(IS_SIMPLE_REL(rel)); + + /* + * If the partitioned relation has any baserestrictinfo quals then we + * attempt to use these quals to prune away partitions that cannot + * possibly contain any tuples matching these quals. In this case we'll + * store the relids of all partitions which could possibly contain a + * matching tuple, and skip anything else in the loop below. + */ + if (enable_partition_pruning && + rte->relkind == RELKIND_PARTITIONED_TABLE && + rel->baserestrictinfo != NIL) + { + live_children = prune_append_rel_partitions(rel); + did_pruning = true; + } + + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + List *childquals; + Index cq_min_security; + bool have_const_false_cq; + PlannerInfo *subroot; + ListCell *lc; + List *translated_exprs, + *child_target_exprs; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + + /* + * The child rel's RelOptInfo was already created during + * add_base_rels_to_query. + */ + childrel = find_base_rel(root, childRTindex); + Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = NIL; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, + (Node *) rinfo->clause, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + /* track minimum security level among child quals */ + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might have + * securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals, see expand_inherited_rtentry().) Pull any such + * securityQuals up into the baserestrictinfo for the child. This is + * similar to process_security_barrier_quals() for the parent rel, + * except that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* not likely that we'd see constants here, so no check */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, false, false, + security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* + * OK, we've got all the baserestrictinfo quals for this child. + */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) + { + /* This partition was pruned; skip it. */ + set_dummy_rel_pathlist(childrel); + continue; + } + + if (relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* This child is excluded; skip it. */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Add missing Vars to child's reltarget. + * + * add_inherit_target_child_root() would've added only those that are + * needed to be present in the top-level tlist (or ones that + * preprocess_targetlist thinks are needed to be in the tlist.) We + * may need other attributes such as those contained in WHERE clauses, + * which are already computed for the parent during + * deconstruct_jointree processing of the original query (child's + * query never goes through deconstruct_jointree.) + */ + translated_exprs = (List *) + adjust_appendrel_attrs(root, + (Node *) rel->reltarget->exprs, + 1, &appinfo); + child_target_exprs = childrel->reltarget->exprs; + foreach(lc, translated_exprs) + { + Expr *expr = lfirst(lc); + + if (!list_member(child_target_exprs, expr)) + child_target_exprs = lappend(child_target_exprs, expr); + } + + subroot = root->inh_target_child_roots[childRTindex]; + + /* + * Also, We have to find any ECs containing parent's expressions and + * *replace* them with their copies containing child expressions. + */ + if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) + { + /* + * First make copies for ECs for the child's root, because some + * might be modified to replace parent EC expressions by child + * expressions in add_child_rel_equivalences. + */ + subroot->eq_classes = NIL; + foreach(lc, root->eq_classes) + { + EquivalenceClass *ec = lfirst(lc); + EquivalenceClass *new_ec = makeNode(EquivalenceClass); + + /* + * First memcpy which creates shallow copies of all + * the members and then make copies of members that could + * change. + * + * XXX comment in _copyPathKey says it's OK to recycle EC + * pointers, but as long as we do the whole planning for a + * given child using a given root, copying ECs like this + * shouldn't be a problem. Maybe, the following code could + * be in _copyEquivalenceClass()? + */ + memcpy(new_ec, ec, sizeof(EquivalenceClass)); + new_ec->ec_opfamilies = list_copy(ec->ec_opfamilies); + new_ec->ec_members = list_copy(ec->ec_members); + new_ec->ec_sources = list_copy(ec->ec_sources); + new_ec->ec_derives = list_copy(ec->ec_derives); + new_ec->ec_relids = bms_copy(ec->ec_relids); + + subroot->eq_classes = lappend(subroot->eq_classes, new_ec); + } + + add_child_rel_equivalences(subroot, appinfo, rel, childrel, true); + } + childrel->has_eclass_joins = rel->has_eclass_joins; + + /* Translate join quals. */ + childrel->joininfo = (List *) + adjust_appendrel_attrs(subroot, + (Node *) rel->joininfo, + 1, &appinfo); + + /* + * If parallelism is allowable for this query in general, see whether + * it's allowable for this childrel in particular. For consistency, + * do this before calling set_rel_size() for the child. + */ + if (root->glob->parallelModeOK) + set_rel_consider_parallel(subroot, childrel, childRTE); + + /* + * Compute the child's size using possibly modified subroot. + */ + set_rel_size(subroot, childrel, childRTindex, childRTE); + + /* If the child itself is partitioned it may turn into a dummy rel. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* We have at least one live child. */ + has_live_children = true; + + Assert(childrel->rows > 0); + } + + if (has_live_children) + { + /* + * Set a non-zero value here to cope with the caller's requirement + * that non-dummy relations are actually not empty. We don't try to + * be accurate here, because we're not going to create a path that + * combines the children outputs. + */ + rel->rows = 1; + } + else + { + /* + * All children were excluded by constraints, so mark the relation + * ass dummy. We must do this in this phase so that the rel's + * dummy-ness is visible when we generate paths for other rels. + */ + set_dummy_rel_pathlist(rel); + } +} + +/* + * set_inherit_target_rel_pathlists + * Build access paths for the child target relations + * + * Similar to set_append_rel_pathlist, except that we build paths of the + * children, but don't build an Append path. + */ +static void +set_inherit_target_rel_pathlists(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + ListCell *l; + + /* Nothing to do if all the children were excluded. */ + if (IS_DUMMY_REL(rel)) + return; + + /* Generate access paths for each of the children of passed-in rel */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + PlannerInfo *subroot; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + /* Re-locate the child RTE and RelOptInfo */ + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + childrel = root->simple_rel_array[childRTindex]; + subroot = root->inh_target_child_roots[childRTindex]; + /* Transfer the value from main root to subroot. */ + subroot->total_table_pages = root->total_table_pages; + + /* + * If set_append_rel_size() decided the parent appendrel was + * parallel-unsafe at some point after visiting this child rel, we + * need to propagate the unsafety marking down to the child, so that + * we don't generate useless partial paths for it. + */ + if (!rel->consider_parallel) + childrel->consider_parallel = false; + + /* + * Compute the child's access paths. + */ + set_rel_pathlist(subroot, childrel, childRTindex, childRTE); + } +} + +/* * set_append_rel_size * Set size estimates for a simple "append relation" * @@ -1046,7 +1453,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, * EquivalenceClass data structures. */ if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) - add_child_rel_equivalences(root, appinfo, rel, childrel); + add_child_rel_equivalences(root, appinfo, rel, childrel, + false); childrel->has_eclass_joins = rel->has_eclass_joins; /* @@ -2625,6 +3033,140 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows) } /* + * inheritance_make_rel_from_joinlist + * Perform join planning for all non-dummy leaf inheritance children + * in their role as an UPDATE/DELETE query's target relation + * + * If a child relation is a partitioned table, its children are processed in + * turn by recursively calling this function. + */ +static void +inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; +#ifdef USE_ASSERT_CHECKING + Relids all_baserels; +#endif + + /* For UPDATE/DELETE queries, the top parent can only ever be a table. */ + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + Assert(planner_rt_fetch(resultRelation, root)->rtekind == RTE_RELATION); + + /* Nothing to do. */ + if (IS_DUMMY_REL(find_base_rel(root, resultRelation))) + return; + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + PlannerInfo *subroot; + RelOptInfo *childrel; + RelOptInfo *childjoinrel; + List *translated_joinlist; + + if (appinfo->parent_relid != resultRelation) + continue; + + childrel = find_base_rel(root, appinfo->child_relid); + + /* Ignore excluded/pruned children. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* Add this child. */ + root->inh_target_child_rels = lappend_int(root->inh_target_child_rels, + appinfo->child_relid); + + /* Perform join planning with child subroot. */ + subroot = root->inh_target_child_roots[appinfo->child_relid]; + Assert(subroot->parse->resultRelation > 0); + + /* + * Modify joinlist such that relations joined to the top parent rel + * appear to be joined to the child rel instead. Do the same for + * any SpecialJoinInfo structs. + */ + translated_joinlist = (List *) + adjust_appendrel_attrs(subroot, + (Node *) joinlist, + 1, &appinfo); + subroot->join_info_list = (List *) + adjust_appendrel_attrs(subroot, + (Node *) root->join_info_list, + 1, &appinfo); + + /* + * Sub-partitioned tables have to be processed recursively using the + * translated subroot as the parent, because AppendRelInfos link + * sub-partitions to their immediate parents, not the root partitioned + * table. + */ + if (childrel->part_scheme != NULL) + { + inheritance_make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * Add this child relation as a placeholder in the parent root's + * inh_target_child_joinrels so that inheritance_planner see same + * number of entries as inh_target_child_rels. + */ + root->inh_target_child_joinrels = + lappend(root->inh_target_child_joinrels, childrel); + + /* Also propagate this child's own children into parent's list. */ + if (subroot->inh_target_child_rels != NIL) + { + root->inh_target_child_rels = + list_concat(root->inh_target_child_rels, + subroot->inh_target_child_rels); + root->inh_target_child_joinrels = + list_concat(root->inh_target_child_joinrels, + subroot->inh_target_child_joinrels); + } + continue; + } + + /* + * Since we added the child rel directly into the join tree, we must + * modify it to be a "base" rel instead of an "other" rel, which the + * join planning code expects the relations being joined to be. + */ + childrel->reloptkind = RELOPT_BASEREL; + + Assert(subroot->join_rel_list == NIL); + Assert(subroot->join_rel_hash == NULL); + + /* Perform join planning and save the resulting RelOptInfo. */ + childjoinrel = make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * Remember this child target rel. inheritance_planner will perform + * the remaining steps of planning for each child relation separately. + * Specifically, it will call grouping_planner on every + * RelOptInfo contained in the inh_target_child_rels list, each of + * which represents the source of tuples to be modified for a given + * target child rel. + */ + root->inh_target_child_joinrels = + lappend(root->inh_target_child_joinrels, childjoinrel); +#ifdef USE_ASSERT_CHECKING + /* + * The following implements essentially the same Assert as in + * make_one_rel, our caller. + */ + all_baserels = bms_copy(root->all_baserels); + all_baserels = bms_del_member(all_baserels, + root->parse->resultRelation); + all_baserels = bms_add_member(all_baserels, + subroot->parse->resultRelation); + Assert(bms_equal(childjoinrel->relids, all_baserels)); +#endif + } +} + +/* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. * diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 6e134ae1d2..10136ef940 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -2104,12 +2104,16 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root, * * parent_rel and child_rel could be derived from appinfo, but since the * caller has already computed them, we might as well just pass them in. + * + * If 'replace' is true then the child EC members *replace* the corresponding + * parent members. */ void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, - RelOptInfo *child_rel) + RelOptInfo *child_rel, + bool replace) { ListCell *lc1; @@ -2117,14 +2121,23 @@ add_child_rel_equivalences(PlannerInfo *root, { EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc1); ListCell *lc2; + ListCell *prev; + ListCell *next; /* * If this EC contains a volatile expression, then generating child * EMs would be downright dangerous, so skip it. We rely on a - * volatile EC having only one EM. + * volatile EC having only one EM. If the caller asked to *replace* + * the original parent expression with the child one, then it's + * okay to proceed, because we wouldn't be adding another expression + * as being equivalent in that case. */ if (cur_ec->ec_has_volatile) - continue; + { + Assert(list_length(cur_ec->ec_members) == 1); + if (!replace) + continue; + } /* * No point in searching if parent rel not mentioned in eclass; but we @@ -2134,12 +2147,18 @@ add_child_rel_equivalences(PlannerInfo *root, !bms_is_subset(parent_rel->relids, cur_ec->ec_relids)) continue; - foreach(lc2, cur_ec->ec_members) + prev = NULL; + for (lc2 = list_head(cur_ec->ec_members); lc2 != NULL; lc2 = next) { EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2); + next = lnext(lc2); + if (cur_em->em_is_const) + { + prev = lc2; continue; /* ignore consts here */ + } /* Does it reference parent_rel? */ if (bms_overlap(cur_em->em_relids, parent_rel->relids)) @@ -2177,10 +2196,38 @@ add_child_rel_equivalences(PlannerInfo *root, child_rel->relids); } + /* + * Caller may have asked us to replace the parent expression + * with the child expression, so delete the parent expression + * first. + */ + if (replace) + cur_ec->ec_members = list_delete_cell(cur_ec->ec_members, + lc2, prev); + + /* + * If we're replacing, then the new member isn't really a + * a child, so em_is_child should be set to false. + */ (void) add_eq_member(cur_ec, child_expr, new_relids, new_nullable_relids, - true, cur_em->em_datatype); + !replace, cur_em->em_datatype); } + else + prev = lc2; + } + + if (replace) + { + /* + * Now fix up EC's relids set. It's OK to modify EC like this, + * because caller must have made a copy of the original EC. + * For example, see adjust_inherited_target_child_root. + */ + cur_ec->ec_relids = bms_difference(cur_ec->ec_relids, + parent_rel->relids); + cur_ec->ec_relids = bms_add_members(cur_ec->ec_relids, + child_rel->relids); } } } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 97d0c28132..1e1c7efef9 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -2019,12 +2019,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) /* * During setrefs.c, we'll need the grouping_map to fix up the cols lists * in GroupingFunc nodes. Save it for setrefs.c to use. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be grouping in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->grouping_map == NULL); root->grouping_map = grouping_map; @@ -2181,12 +2176,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) * with InitPlan output params. (We can't just do that locally in the * MinMaxAgg node, because path nodes above here may have Agg references * as well.) Save the mmaggregates list to tell setrefs.c to do that. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be aggregates in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->minmax_aggs == NIL); root->minmax_aggs = best_path->mmaggregates; diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index fc97a1bb50..78557aeecb 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -28,7 +28,7 @@ #include "optimizer/paths.h" #include "optimizer/placeholder.h" #include "optimizer/planmain.h" - +#include "optimizer/prep.h" /* * query_planner @@ -59,6 +59,7 @@ query_planner(PlannerInfo *root, List *tlist, Query *parse = root->parse; List *joinlist; RelOptInfo *final_rel; + Index rti; /* * If the query has an empty join tree, then it's something easy like @@ -232,13 +233,52 @@ query_planner(PlannerInfo *root, List *tlist, extract_restriction_or_clauses(root); /* + * Construct the all_baserels Relids set. + */ + root->all_baserels = NULL; + for (rti = 1; rti < root->simple_rel_array_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + Assert(brel->relid == rti); /* sanity check on array */ + + /* ignore RTEs that are "other rels" */ + if (brel->reloptkind != RELOPT_BASEREL) + continue; + + root->all_baserels = bms_add_member(root->all_baserels, brel->relid); + } + + /* + * Add child subroots needed to use during planning for individual child + * targets + */ + if (root->inherited_update) + { + root->inh_target_child_roots = (PlannerInfo **) + palloc0(root->simple_rel_array_size * + sizeof(PlannerInfo *)); + add_inherited_target_child_roots(root); + } + + /* * Ready to do the primary planning. */ final_rel = make_one_rel(root, joinlist); - /* Check that we got at least one usable path */ - if (!final_rel || !final_rel->cheapest_total_path || - final_rel->cheapest_total_path->param_info != NULL) + /* + * Check that we got at least one usable path. In the case of an + * inherited update/delete operation, no path has been created for + * the query's actual target relation yet. + */ + if (!root->inherited_update && + (!final_rel || + !final_rel->cheapest_total_path || + final_rel->cheapest_total_path->param_info != NULL)) elog(ERROR, "failed to construct the join relation"); return final_rel; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 5ba612922f..703babfdf0 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -39,6 +39,7 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "nodes/relation.h" #include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" @@ -131,7 +132,7 @@ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static void inheritance_planner(PlannerInfo *root); static void grouping_planner(PlannerInfo *root, bool inheritance_update, - double tuple_fraction); + RelOptInfo *planned_rel, double tuple_fraction); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, int *tleref_to_colnum_map); @@ -635,7 +636,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->grouping_map = NULL; root->minmax_aggs = NIL; root->qual_security_level = 0; - root->inhTargetKind = INHKIND_NONE; root->hasRecursion = hasRecursion; if (hasRecursion) root->wt_param_id = assign_special_exec_param(root); @@ -725,6 +725,13 @@ subquery_planner(PlannerGlobal *glob, Query *parse, expand_inherited_tables(root); /* + * Now that we have figured out "actual" inheritance situation of the + * relations, set whether the query is an inherited UPDATE/DELETE. + */ + root->inherited_update = (parse->resultRelation && + rt_fetch(parse->resultRelation, parse->rtable)->inh); + + /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -975,7 +982,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh) inheritance_planner(root); else - grouping_planner(root, false, tuple_fraction); + grouping_planner(root, false, NULL, tuple_fraction); /* * Capture the set of outer-level param IDs we have access to, for use in @@ -1151,13 +1158,41 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr) * inheritance set. * * We have to handle this case differently from cases where a source relation - * is an inheritance set. Source inheritance is expanded at the bottom of the - * plan tree (see allpaths.c), but target inheritance has to be expanded at - * the top. The reason is that for UPDATE, each target relation needs a - * different targetlist matching its own column set. Fortunately, + * is an inheritance set, where outputs of all the children are combined and + * combined output consumed using the source relation's (parent's) column set. + * For UPDATE, each target relation, where the query's output will go, needs + * a different targetlist matching its own column set. So, we will need to + * translate the query such that it produces the desired output required by + * each of the child target relations and plan each separately. Fortunately, * the UPDATE/DELETE target can never be the nullable side of an outer join, * so it's OK to generate the plan this way. * + * Planning steps that need to be performed separately for each child and + * any preliminary processing needed for that are handled by the following + * sub-routines of query_planner: + * + * add_inherit_target_roots - this creates copies of PlannerInfo for each + * child after query_planner has finished processing the join tree and + * creating EquivalenceClasses, so that working state of planning need + * not be created afresh for each child, especially the various arrays + * + * set_inherit_target_rel_sizes - this sets size estimates for child + * relations, replace the parent EC members by corresponding child ones + * in their respective subroots + * + * set_inherit_target_rel_pathlists - this creates Paths for scanning + * individual child relations + * + * inherit_make_rel_from_joinlist - this translates the jointree, replacing + * the target relation in the original jointree (the root parent) by + * individual child target relations and performs join planning on the + * resulting join tree, saving the RelOptInfos of resulting join relations + * into the top-level PlannerInfo + * + * Finally, here we apply grouping_planner to each child join relation + * produced by set_inherit_target_rel_pathlists to so that its path produces + * the desired targetlist. + * * Returns nothing; the useful output is in the Paths we attach to * the (UPPERREL_FINAL, NULL) upperrel stored in *root. * @@ -1169,14 +1204,8 @@ inheritance_planner(PlannerInfo *root) { Query *parse = root->parse; int top_parentRTindex = parse->resultRelation; - Bitmapset *subqueryRTindexes; - Bitmapset *modifiableARIindexes; int nominalRelation = -1; Index rootRelation = 0; - List *final_rtable = NIL; - int save_rel_array_size = 0; - RelOptInfo **save_rel_array = NULL; - AppendRelInfo **save_append_rel_array = NULL; List *subpaths = NIL; List *subroots = NIL; List *resultRelations = NIL; @@ -1184,70 +1213,47 @@ inheritance_planner(PlannerInfo *root) List *returningLists = NIL; List *rowMarks; RelOptInfo *final_rel; - ListCell *lc; - Index rti; + ListCell *lc1, + *lc2; RangeTblEntry *parent_rte; - PlannerInfo *parent_root; - Query *parent_parse; - Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); - PlannerInfo **parent_roots = NULL; + List *tlist; + standard_qp_extra qp_extra; + RelOptInfo *planned_rel; + /* Inheritance is never used for insert. */ Assert(parse->commandType != CMD_INSERT); /* - * We generate a modified instance of the original Query for each target - * relation, plan that, and put all the plans into a list that will be - * controlled by a single ModifyTable node. All the instances share the - * same rangetable, but each instance must have its own set of subquery - * RTEs within the finished rangetable because (1) they are likely to get - * scribbled on during planning, and (2) it's not inconceivable that - * subqueries could get planned differently in different cases. We need - * not create duplicate copies of other RTE kinds, in particular not the - * target relations, because they don't have either of those issues. Not - * having to duplicate the target relations is important because doing so - * (1) would result in a rangetable of length O(N^2) for N targets, with - * at least O(N^3) work expended here; and (2) would greatly complicate - * management of the rowMarks list. + * Let query_planner generate the access paths for the query for each + * target child relation. * - * To begin with, generate a bitmapset of the relids of the subquery RTEs. + * First, save the unexpanded version of the query's targetlist. + * create_inherit_target_child_root will use it as base when expanding + * it for a given child relation as the query's target relation instead + * of the parent. */ - subqueryRTindexes = NULL; - rti = 1; - foreach(lc, parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - if (rte->rtekind == RTE_SUBQUERY) - subqueryRTindexes = bms_add_member(subqueryRTindexes, rti); - rti++; - } + root->unexpanded_tlist = list_copy(root->parse->targetList); + tlist = preprocess_targetlist(root); + root->processed_tlist = tlist; + qp_extra.tlist = tlist; + qp_extra.activeWindows = NIL; + qp_extra.groupClause = NIL; + planned_rel = query_planner(root, tlist, standard_qp_callback, &qp_extra); /* - * Next, we want to identify which AppendRelInfo items contain references - * to any of the aforesaid subquery RTEs. These items will need to be - * copied and modified to adjust their subquery references; whereas the - * other ones need not be touched. It's worth being tense over this - * because we can usually avoid processing most of the AppendRelInfo - * items, thereby saving O(N^2) space and time when the target is a large - * inheritance tree. We can identify AppendRelInfo items by their - * child_relid, since that should be unique within the list. + * If it turned out during query planning that all the children are dummy + * (pruned or excluded by constraints), no need to do the steps below. + * Let grouping_planner finish up the final path. */ - modifiableARIindexes = NULL; - if (subqueryRTindexes != NULL) + if (IS_DUMMY_REL(planned_rel)) { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - - if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) || - bms_is_member(appinfo->child_relid, subqueryRTindexes) || - bms_overlap(pull_varnos((Node *) appinfo->translated_vars), - subqueryRTindexes)) - modifiableARIindexes = bms_add_member(modifiableARIindexes, - appinfo->child_relid); - } + grouping_planner(root, false, planned_rel, 0.0); + return; } + Assert(planned_rel->relid == top_parentRTindex); + Assert(planned_rel->reloptkind == RELOPT_BASEREL); + /* * If the parent RTE is a partitioned table, we should use that as the * nominal target relation, because the RTEs added for partitioned tables @@ -1255,7 +1261,7 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); + parent_rte = planner_rt_fetch(top_parentRTindex, root); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1263,95 +1269,39 @@ inheritance_planner(PlannerInfo *root) } /* - * The PlannerInfo for each child is obtained by translating the relevant - * members of the PlannerInfo for its immediate parent, which we find - * using the parent_relid in its AppendRelInfo. We save the PlannerInfo - * for each parent in an array indexed by relid for fast retrieval. Since - * the maximum number of parents is limited by the number of RTEs in the - * query, we use that number to allocate the array. An extra entry is - * needed since relids start from 1. + * Get on with finalizing the path for each child target relation by + * calling grouping_planner on its joinrel. */ - parent_roots = (PlannerInfo **) palloc0((list_length(parse->rtable) + 1) * - sizeof(PlannerInfo *)); - parent_roots[top_parentRTindex] = root; - - /* - * And now we can get on with generating a plan for each child table. - */ - foreach(lc, root->append_rel_list) + forboth(lc1, root->inh_target_child_rels, + lc2, root->inh_target_child_joinrels) { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - PlannerInfo *subroot; + Index childRTindex = lfirst_int(lc1); + RelOptInfo *childjoinrel = lfirst(lc2); + PlannerInfo *subroot = root->inh_target_child_roots[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; RangeTblEntry *child_rte; RelOptInfo *sub_final_rel; Path *subpath; - /* append_rel_list contains all append rels; ignore others */ - if (!bms_is_member(appinfo->parent_relid, parent_relids)) - continue; - - /* - * expand_inherited_rtentry() always processes a parent before any of - * that parent's children, so the parent_root for this relation should - * already be available. - */ - parent_root = parent_roots[appinfo->parent_relid]; - Assert(parent_root != NULL); - parent_parse = parent_root->parse; - - /* - * We need a working copy of the PlannerInfo so that we can control - * propagation of information back to the main copy. - */ - subroot = makeNode(PlannerInfo); - memcpy(subroot, parent_root, sizeof(PlannerInfo)); - - /* - * Generate modified query with this rel as target. We first apply - * adjust_appendrel_attrs, which copies the Query and changes - * references to the parent RTE to refer to the current child RTE, - * then fool around with subquery RTEs. - */ - subroot->parse = (Query *) - adjust_appendrel_attrs(parent_root, - (Node *) parent_parse, - 1, &appinfo); + Assert(subroot != NULL); + Assert(subroot->parse->resultRelation == childRTindex); /* * If there are securityQuals attached to the parent, move them to the * child rel (they've already been transformed properly for that). */ - parent_rte = rt_fetch(appinfo->parent_relid, subroot->parse->rtable); - child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable); + parent_rte = planner_rt_fetch(appinfo->parent_relid, subroot); + child_rte = planner_rt_fetch(appinfo->child_relid, subroot); child_rte->securityQuals = parent_rte->securityQuals; parent_rte->securityQuals = NIL; /* - * Mark whether we're planning a query to a partitioned table or an - * inheritance parent. + * Ignore a partitioned child. Instead, the paths of its children will + * be added to subpaths. */ - subroot->inhTargetKind = - (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED; - - /* - * If this child is further partitioned, remember it as a parent. - * Since a partitioned table does not have any data, we don't need to - * create a plan for it, and we can stop processing it here. We do, - * however, need to remember its modified PlannerInfo for use when - * processing its children, since we'll update their varnos based on - * the delta from immediate parent to child, not from top to child. - * - * Note: a very non-obvious point is that we have not yet added - * duplicate subquery RTEs to the subroot's rtable. We mustn't, - * because then its children would have two sets of duplicates, - * confusing matters. - */ - if (child_rte->inh) + if (childjoinrel->part_scheme) { - Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE); - parent_relids = bms_add_member(parent_relids, appinfo->child_relid); - parent_roots[appinfo->child_relid] = subroot; - + Assert(child_rte->inh); continue; } @@ -1379,111 +1329,8 @@ inheritance_planner(PlannerInfo *root) if (nominalRelation < 0) nominalRelation = appinfo->child_relid; - /* - * The rowMarks list might contain references to subquery RTEs, so - * make a copy that we can apply ChangeVarNodes to. (Fortunately, the - * executor doesn't need to see the modified copies --- we can just - * pass it the original rowMarks list.) - */ - subroot->rowMarks = copyObject(parent_root->rowMarks); - - /* - * The append_rel_list likewise might contain references to subquery - * RTEs (if any subqueries were flattenable UNION ALLs). So prepare - * to apply ChangeVarNodes to that, too. As explained above, we only - * want to copy items that actually contain such references; the rest - * can just get linked into the subroot's append_rel_list. - * - * If we know there are no such references, we can just use the outer - * append_rel_list unmodified. - */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - subroot->append_rel_list = NIL; - foreach(lc2, parent_root->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, modifiableARIindexes)) - appinfo2 = copyObject(appinfo2); - - subroot->append_rel_list = lappend(subroot->append_rel_list, - appinfo2); - } - } - - /* - * Add placeholders to the child Query's rangetable list to fill the - * RT indexes already reserved for subqueries in previous children. - * These won't be referenced, so there's no need to make them very - * valid-looking. - */ - while (list_length(subroot->parse->rtable) < list_length(final_rtable)) - subroot->parse->rtable = lappend(subroot->parse->rtable, - makeNode(RangeTblEntry)); - - /* - * If this isn't the first child Query, generate duplicates of all - * subquery RTEs, and adjust Var numbering to reference the - * duplicates. To simplify the loop logic, we scan the original rtable - * not the copy just made by adjust_appendrel_attrs; that should be OK - * since subquery RTEs couldn't contain any references to the target - * rel. - */ - if (final_rtable != NIL && subqueryRTindexes != NULL) - { - ListCell *lr; - - rti = 1; - foreach(lr, parent_parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lr); - - if (bms_is_member(rti, subqueryRTindexes)) - { - Index newrti; - - /* - * The RTE can't contain any references to its own RT - * index, except in its securityQuals, so we can save a - * few cycles by applying ChangeVarNodes to the rest of - * the rangetable before we append the RTE to it. - */ - newrti = list_length(subroot->parse->rtable) + 1; - ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); - ChangeVarNodes((Node *) subroot->rowMarks, rti, newrti, 0); - /* Skip processing unchanging parts of append_rel_list */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - foreach(lc2, subroot->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, - modifiableARIindexes)) - ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); - } - } - rte = copyObject(rte); - ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); - subroot->parse->rtable = lappend(subroot->parse->rtable, - rte); - } - rti++; - } - } - - /* There shouldn't be any OJ info to translate, as yet */ - Assert(subroot->join_info_list == NIL); - /* and we haven't created PlaceHolderInfos, either */ - Assert(subroot->placeholder_list == NIL); - - /* Generate Path(s) for accessing this result relation */ - grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ ); + /* Apply the top-level targetlist to childjoinrel's path. */ + grouping_planner(subroot, true, childjoinrel, 0.0); /* * Select cheapest path in case there's more than one. We always run @@ -1495,45 +1342,10 @@ inheritance_planner(PlannerInfo *root) subpath = sub_final_rel->cheapest_total_path; /* - * If this child rel was excluded by constraint exclusion, exclude it - * from the result plan. + * child rel cannot be empty, or inheritance_make_rel_from_joinlist + * wouldn't have put it in the list to begin with. */ - if (IS_DUMMY_PATH(subpath)) - continue; - - /* - * If this is the first non-excluded child, its post-planning rtable - * becomes the initial contents of final_rtable; otherwise, append - * just its modified subquery RTEs to final_rtable. - */ - if (final_rtable == NIL) - final_rtable = subroot->parse->rtable; - else - final_rtable = list_concat(final_rtable, - list_copy_tail(subroot->parse->rtable, - list_length(final_rtable))); - - /* - * We need to collect all the RelOptInfos from all child plans into - * the main PlannerInfo, since setrefs.c will need them. We use the - * last child's simple_rel_array (previous ones are too short), so we - * have to propagate forward the RelOptInfos that were already built - * in previous children. - */ - Assert(subroot->simple_rel_array_size >= save_rel_array_size); - for (rti = 1; rti < save_rel_array_size; rti++) - { - RelOptInfo *brel = save_rel_array[rti]; - - if (brel) - subroot->simple_rel_array[rti] = brel; - } - save_rel_array_size = subroot->simple_rel_array_size; - save_rel_array = subroot->simple_rel_array; - save_append_rel_array = subroot->append_rel_array; - - /* Make sure any initplans from this rel get into the outer list */ - root->init_plans = subroot->init_plans; + Assert(!IS_DUMMY_PATH(subpath)); /* Build list of sub-paths */ subpaths = lappend(subpaths, subpath); @@ -1565,36 +1377,6 @@ inheritance_planner(PlannerInfo *root) */ /* - * If we managed to exclude every child rel, return a dummy plan; it - * doesn't even need a ModifyTable node. - */ - if (subpaths == NIL) - { - set_dummy_rel_pathlist(final_rel); - return; - } - - /* - * Put back the final adjusted rtable into the master copy of the Query. - * (We mustn't do this if we found no non-excluded children.) - */ - parse->rtable = final_rtable; - root->simple_rel_array_size = save_rel_array_size; - root->simple_rel_array = save_rel_array; - root->append_rel_array = save_append_rel_array; - - /* Must reconstruct master's simple_rte_array, too */ - root->simple_rte_array = (RangeTblEntry **) - palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *)); - rti = 1; - foreach(lc, final_rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - root->simple_rte_array[rti++] = rte; - } - - /* * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will * have dealt with fetching non-locked marked rows, else we need to have * ModifyTable do that. @@ -1634,6 +1416,12 @@ inheritance_planner(PlannerInfo *root) * (inheritance_planner will create a single ModifyTable node covering all the * target tables.) * + * If non-NULL, planned_rel is a RelOptInfo containing paths for the query's + * top-level joinrel, which the caller produced by itself. In that case, this + * function only needs to adjust the targetlist of its cheapest_total_path. + * The only caller that may pass such a RelOptInfo currently is + * inheritance_planner. + * * tuple_fraction is the fraction of tuples we expect will be retrieved. * tuple_fraction is interpreted as follows: * 0: expect all tuples to be retrieved (normal case) @@ -1652,6 +1440,7 @@ inheritance_planner(PlannerInfo *root) */ static void grouping_planner(PlannerInfo *root, bool inheritance_update, + RelOptInfo *planned_rel, double tuple_fraction) { Query *parse = root->parse; @@ -1664,7 +1453,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, List *final_targets; List *final_targets_contain_srfs; bool final_target_parallel_safe; - RelOptInfo *current_rel; + RelOptInfo *current_rel = planned_rel; RelOptInfo *final_rel; ListCell *lc; @@ -1704,6 +1493,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * special work for recursive unions is the responsibility of * plan_set_operations. */ + Assert(current_rel == NULL); current_rel = plan_set_operations(root); /* @@ -1793,17 +1583,26 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, parse->groupClause = preprocess_groupclause(root, NIL); } - /* Preprocess targetlist */ - tlist = preprocess_targetlist(root); - /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation so - * that we can transfer its decoration (resnames etc) to the topmost - * tlist of the finished Plan. + * Preprocess targetlist, if needed. If the caller has already done + * query planning, root->processed_tlist already contains the desired + * targetlist. */ - root->processed_tlist = tlist; + if (planned_rel == NULL) + { + tlist = preprocess_targetlist(root); + + /* + * We are now done hacking up the query's targetlist. Most of the + * remaining planning work will be done with the PathTarget + * representation of tlists, but save aside the full representation + * so that we can transfer its decoration (resnames etc) to the + * topmost tlist of the finished Plan. + */ + root->processed_tlist = tlist; + } + else + tlist = root->processed_tlist; /* * Collect statistics about aggregates for estimating costs, and mark @@ -1883,8 +1682,9 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * We also generate (in standard_qp_callback) pathkey representations * of the query's sort clause, distinct clause, etc. */ - current_rel = query_planner(root, tlist, - standard_qp_callback, &qp_extra); + if (current_rel == NULL) + current_rel = query_planner(root, tlist, + standard_qp_callback, &qp_extra); /* * Convert the query's result tlist into PathTarget format. diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 77dbf4eba3..87adfa02b1 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -917,7 +917,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->grouping_map = NULL; subroot->minmax_aggs = NIL; subroot->qual_security_level = 0; - subroot->inhTargetKind = INHKIND_NONE; subroot->hasRecursion = false; subroot->wt_param_id = -1; subroot->non_recursive_path = NULL; diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index ca6622ece9..34a3ebbbe4 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -394,8 +394,39 @@ adjust_appendrel_attrs_mutator(Node *node, context->appinfos); return (Node *) phv; } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index db474acbc5..0d2b42f72e 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -21,11 +21,14 @@ #include "miscadmin.h" #include "optimizer/appendinfo.h" #include "optimizer/inherit.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "utils/rel.h" +static PlannerInfo *create_inherited_target_child_root(PlannerInfo *root, + AppendRelInfo *appinfo); static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti); static void expand_partitioned_rtentry(PlannerInfo *root, @@ -492,3 +495,109 @@ translate_col_privs(const Bitmapset *parent_privs, return child_privs; } + +/* + * add_inherited_target_child_roots + * For each child of of the query's result relation, this translates the + * original query to match the child and creates a PlannerInfo containing + * the translated query + * + * Child PlannerInfo reuses most of the parent PlannerInfo's fields unchanged, + * except unexpanded_tlist, processed_tlist, and all_baserels, all of which + * are based on the child relation. + */ +void +add_inherited_target_child_roots(PlannerInfo *root) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; + + Assert(root->inh_target_child_roots != NULL); + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + RangeTblEntry *childRTE; + PlannerInfo *subroot; + + if (appinfo->parent_relid != resultRelation) + continue; + + /* + * Create a PlannerInfo for processing this child target relation + * with. + */ + subroot = create_inherited_target_child_root(root, appinfo); + root->inh_target_child_roots[appinfo->child_relid] = subroot; + + /* + * If the child is a partitioned table, recurse to do this for its + * partitions. + */ + childRTE = root->simple_rte_array[appinfo->child_relid]; + if (childRTE->inh) + add_inherited_target_child_roots(subroot); + } +} + +/* + * create_inherited_target_child_root + * Workhorse of add_inherited_target_child_roots + */ +static PlannerInfo * +create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) +{ + PlannerInfo *subroot; + List *tlist; + + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + + /* + * Translate the original query to replace Vars of the parent table + * by the corresponding Vars of the child table and to make child the main + * target relation of the query. + */ + subroot = makeNode(PlannerInfo); + memcpy(subroot, root, sizeof(PlannerInfo)); + + /* + * Restore the original, unexpanded targetlist, that is, the one before + * preprocess_targetlist was run on the original query. We'll run + * preprocess_targetlist after translating the query and the targetlist, + * so that it is expanded according to child's tuple descriptor. + */ + root->parse->targetList = root->unexpanded_tlist; + subroot->parse = (Query *) adjust_appendrel_attrs(root, + (Node *) root->parse, + 1, &appinfo); + + /* Save the just translated targetlist as unexpanded_tlist in the child's + * subroot, so that this child's own children can use it. Must use copy + * because subroot->parse->targetList will be modified soon. + */ + subroot->unexpanded_tlist = list_copy(subroot->parse->targetList); + + /* + * Apply planner's expansion of targetlist, such as adding various junk + * column, filling placeholder entries for dropped columns, etc., all of + * which occurs with the child's TupleDesc. + */ + tlist = preprocess_targetlist(subroot); + subroot->processed_tlist = tlist; + + /* Add any newly added Vars to the child RelOptInfo. */ + build_base_rel_tlists(subroot, tlist); + + /* + * Adjust all_baserels to replace the original target relation with the + * child target relation. Copy it before modifying though. + */ + subroot->all_baserels = bms_copy(root->all_baserels); + subroot->all_baserels = bms_del_member(subroot->all_baserels, + root->parse->resultRelation); + subroot->all_baserels = bms_add_member(subroot->all_baserels, + subroot->parse->resultRelation); + + return subroot; +} diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 48ffc5f254..6009de309e 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1264,36 +1264,6 @@ get_relation_constraints(PlannerInfo *root, } } - /* - * Append partition predicates, if any. - * - * For selects, partition pruning uses the parent table's partition bound - * descriptor, instead of constraint exclusion which is driven by the - * individual partition's partition constraint. - */ - if (enable_partition_pruning && root->parse->commandType != CMD_SELECT) - { - List *pcqual = RelationGetPartitionQual(relation); - - if (pcqual) - { - /* - * Run the partition quals through const-simplification similar to - * check constraints. We skip canonicalize_qual, though, because - * partition quals should be in canonical form already; also, - * since the qual is in implicit-AND format, we'd have to - * explicitly convert it to explicit-AND format and back again. - */ - pcqual = (List *) eval_const_expressions(root, (Node *) pcqual); - - /* Fix Vars to have the desired varno */ - if (varno != 1) - ChangeVarNodes((Node *) pcqual, 1, varno, 0); - - result = list_concat(result, pcqual); - } - } - heap_close(relation, NoLock); return result; @@ -1417,40 +1387,15 @@ relation_excluded_by_constraints(PlannerInfo *root, /* * Skip further tests, depending on constraint_exclusion. */ - switch (constraint_exclusion) - { - case CONSTRAINT_EXCLUSION_OFF: - - /* - * Don't prune if feature turned off -- except if the relation is - * a partition. While partprune.c-style partition pruning is not - * yet in use for all cases (update/delete is not handled), it - * would be a UI horror to use different user-visible controls - * depending on such a volatile implementation detail. Therefore, - * for partitioned tables we use enable_partition_pruning to - * control this behavior. - */ - if (root->inhTargetKind == INHKIND_PARTITIONED) - break; - return false; - - case CONSTRAINT_EXCLUSION_PARTITION: - - /* - * When constraint_exclusion is set to 'partition' we only handle - * OTHER_MEMBER_RELs, or BASERELs in cases where the result target - * is an inheritance parent or a partitioned table. - */ - if ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) && - !(rel->reloptkind == RELOPT_BASEREL && - root->inhTargetKind != INHKIND_NONE && - rel->relid == root->parse->resultRelation)) - return false; - break; - - case CONSTRAINT_EXCLUSION_ON: - break; /* always try to exclude */ - } + if (constraint_exclusion == CONSTRAINT_EXCLUSION_OFF) + return false; + /* + * When constraint_exclusion is set to 'partition' we only handle + * OTHER_MEMBER_RELs. + */ + else if (constraint_exclusion == CONSTRAINT_EXCLUSION_PARTITION && + rel->reloptkind != RELOPT_OTHER_MEMBER_REL) + return false; /* * Check for self-contradictory restriction clauses. We dare not make diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 3430061361..beb2e323a8 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -319,9 +319,6 @@ typedef struct PlannerInfo Index qual_security_level; /* minimum security_level for quals */ /* Note: qual_security_level is zero if there are no securityQuals */ - InheritanceKind inhTargetKind; /* indicates if the target relation is an - * inheritance child or partition or a - * partitioned table */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */ bool hasDeletedRTEs; /* true if any RTE was deleted from jointree */ @@ -343,6 +340,40 @@ typedef struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* + * The following fields are set during query planning portion of an + * inherited UPDATE/DEELETE operation. + */ + bool inherited_update; /* UPDATE/DELETE on inheritance parent? */ + + /* + * This stores the original version of the query's targetlist that's + * not modified by the planner. + */ + List *unexpanded_tlist; + + /* + * Array containing simple_rel_array_size elements, indexed by rangetable + * index (entry 0 is wasted like simple_rel_array). Only elements + * corresponding to individual inheritance child target relations are + * non-NULL. Content of each PlannerInfo is same as the parent + * PlannerInfo, except for the parse tree which is a translated copy of + * the parent's parse tree. + */ + struct PlannerInfo **inh_target_child_roots; + + /* List of RT indexes of child RT indexes. */ + List *inh_target_child_rels; + + /* + * RelOptInfos corresponding to each child target rel. For leaf children, + * it's the RelOptInfo representing the output of make_rel_from_joinlist() + * called with the parent rel in the original join tree replaced by a + * given leaf child. For non-leaf children, it's the baserel RelOptInfo + * itself, left as a placeholder. + */ + List *inh_target_child_joinrels; } PlannerInfo; diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h index b2687abd4e..1dbc2ca5bf 100644 --- a/src/include/optimizer/inherit.h +++ b/src/include/optimizer/inherit.h @@ -18,5 +18,6 @@ extern void expand_inherited_tables(PlannerInfo *root); +extern void add_inherited_target_child_roots(PlannerInfo *root); #endif /* INHERIT_H */ diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 666217c189..f524ab6beb 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -161,7 +161,8 @@ extern EquivalenceClass *match_eclasses_to_foreign_key_col(PlannerInfo *root, extern void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, - RelOptInfo *child_rel); + RelOptInfo *child_rel, + bool replace); extern List *generate_implied_equalities_for_column(PlannerInfo *root, RelOptInfo *rel, ec_matches_callback_type callback, diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index c55de5d476..c9f02c1fb0 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1780,7 +1780,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_1 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_1 -> Nested Loop Semi Join @@ -1788,7 +1788,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_2 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_2 (28 rows) -- 2.11.0
v15-0002-Lazy-creation-of-RTEs-for-inheritance-children.patch (text/plain, 97.8K) download From 6cea5750413fade8ef432c8f9196035dba2d0f99 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 26 Oct 2018 16:45:59 +0900 Subject: [PATCH v15 2/4] Lazy creation of RTEs for inheritance children Currently, expand_inherited_tables called from subquery_planner opens all inheritance child tables and adds them to the query's range table and PlannerInfo in the form of AppendRelInfo's initially and later RelOptInfo's. For partitioning, that's pretty wasteful, because it's possible to determine only the partitions that will need to be scanned using partition pruning, which doesn't require opening the partitions themselves. However, the point at which expand_inherited_tables is called currently is too early to perform partition pruning. This commit rearranges things within the planner so that expand_inherited_tables can be called from query_planner after the latter has finished setting up the information needed to perform partition pruning. However that means the PlannerInfo arrays that store RangeTblEntry's, RelOptInfo's, and AppendRelInfo's need to be expanded if inheritance expansion leads to adding more relations to planning. There are also consequences around targetlist expansion. For example, preprocess_targetlist cannot add the junk columns entries needed for row mark handling until all of the inheritance children have been determined, because different children may need different types of junk columns to be added. So, the approach of late inheritance expansion required some interface tweaks for preprocess_targetlist(). For partitioning, although we don't create a RangeTblEntry and RelOptInfo for pruned partitions at make_one_rel time, partitionwise join code relies on the fact that even though partitions may have been pruned, they'd still own a RelOptInfo to handle the outer join case where the pruned partition appears on the nullable side of join. Partitionwise join code deals with that by allocating dummy RelOptInfos for pruned partitions that are based mostly on their parent's properties. There are some regression test diffs: 1. Caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child, as seen in the partition_aggregate.out test output. 2. Those in postgres_fdw.out are caused by the fact that junk columns required for row marking are added to reltarget->exprs later than user columns, because the row marking junk columns aren't added until the inheritance is expanded which as of this commit is later than it used to be as noted above. --- contrib/postgres_fdw/expected/postgres_fdw.out | 32 +- src/backend/nodes/outfuncs.c | 1 + src/backend/optimizer/path/allpaths.c | 372 ++-------- src/backend/optimizer/path/joinrels.c | 61 ++ src/backend/optimizer/plan/initsplan.c | 58 -- src/backend/optimizer/plan/planmain.c | 10 + src/backend/optimizer/plan/planner.c | 74 +- src/backend/optimizer/plan/setrefs.c | 6 + src/backend/optimizer/prep/preptlist.c | 15 +- src/backend/optimizer/prep/prepunion.c | 3 + src/backend/optimizer/util/appendinfo.c | 45 +- src/backend/optimizer/util/inherit.c | 819 +++++++++++++++------- src/backend/optimizer/util/plancat.c | 47 +- src/backend/optimizer/util/relnode.c | 107 +-- src/backend/partitioning/partprune.c | 43 +- src/include/nodes/relation.h | 18 + src/include/optimizer/appendinfo.h | 7 +- src/include/optimizer/pathnode.h | 1 + src/include/optimizer/plancat.h | 4 +- src/include/optimizer/prep.h | 3 +- src/include/partitioning/partprune.h | 2 +- src/test/regress/expected/partition_aggregate.out | 4 +- 22 files changed, 946 insertions(+), 786 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index bb92d9d37a..33a33606ef 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7072,15 +7072,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7110,15 +7110,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7147,15 +7147,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid @@ -7165,15 +7165,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (39 rows) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 158da68925..67697b8884 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2272,6 +2272,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_BOOL_FIELD(consider_partitionwise_join); WRITE_BITMAPSET_FIELD(top_parent_relids); WRITE_NODE_FIELD(partitioned_child_rels); + WRITE_UINT_FIELD(inh_root_parent); } static void diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 3324d485e4..042d6b2b0d 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -45,7 +45,6 @@ #include "optimizer/var.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -383,6 +382,14 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, else if (rte->inh) { /* + * expand_inherited_tables may have proved that the relation is empty. + * For example, if it's a partitioned table with 0 partitions or all + * of its partitions are pruned. In that case nothing to do here. + */ + if (IS_DUMMY_REL(rel)) + return; + + /* * If it's a target relation, set the sizes of children instead. * Otherwise, we'll append the outputs of children, so process it as * an "append relation". @@ -951,38 +958,18 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, int parentRTindex = rti; bool has_live_children; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); Assert(IS_SIMPLE_REL(rel)); - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - foreach(l, root->append_rel_list) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; PlannerInfo *subroot; ListCell *lc; List *translated_exprs, @@ -994,141 +981,22 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); + Assert(childrel != NULL); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals. */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); + if (IS_DUMMY_REL(childrel)) continue; - } - - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } if (relation_excluded_by_constraints(root, childrel, childRTE)) { @@ -1181,15 +1049,9 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, EquivalenceClass *new_ec = makeNode(EquivalenceClass); /* - * First memcpy which creates shallow copies of all - * the members and then make copies of members that could - * change. - * - * XXX comment in _copyPathKey says it's OK to recycle EC - * pointers, but as long as we do the whole planning for a - * given child using a given root, copying ECs like this - * shouldn't be a problem. Maybe, the following code could - * be in _copyEquivalenceClass()? + * First memcpy the original EC, which creates shallow copies + * of all the members and then make copies of members that + * might change during add_child_rel_equivalences(). */ memcpy(new_ec, ec, sizeof(EquivalenceClass)); new_ec->ec_opfamilies = list_copy(ec->ec_opfamilies); @@ -1332,8 +1194,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -1341,32 +1201,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Assert(IS_SIMPLE_REL(rel)); /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, we will bubble up - * the indexes of partitioned relations that appear down in the tree, so - * that when we've created Paths for all the children, the root - * partitioned table's list will contain all such indexes. - */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - rel->partitioned_child_rels = list_make1_int(rti); - - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - - /* * If this is a partitioned baserel, set the consider_partitionwise_join * flag; currently, we only consider partitionwise joins with the baserel * if its targetlist doesn't contain a whole-row Var. @@ -1403,12 +1237,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; ListCell *parentvars; ListCell *childvars; - ListCell *lc; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1416,18 +1246,35 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); + Assert(childrel != NULL); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); /* - * Copy/Modify targetlist. Even if this child is deemed empty, we need - * its targetlist in case it falls on nullable side in a child-join - * because of partitionwise join. + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals. + */ + if (IS_DUMMY_REL(childrel)) + continue; + + if (relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* + * This child need not be scanned, so we can omit it from the + * appendrel. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Copy/Modify targetlist. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -1457,144 +1304,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, false); childrel->has_eclass_joins = rel->has_eclass_joins; - /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* - * This child need not be scanned, so we can omit it from the - * appendrel. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - /* CE failed, so finish copying/modifying join quals. */ childrel->joininfo = (List *) adjust_appendrel_attrs(root, @@ -3070,6 +2779,7 @@ inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) continue; childrel = find_base_rel(root, appinfo->child_relid); + Assert(childrel != NULL); /* Ignore excluded/pruned children. */ if (IS_DUMMY_REL(childrel)) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 00611a5e40..75edac96f4 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -20,6 +20,7 @@ #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" +#include "optimizer/tlist.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -49,6 +50,9 @@ static SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, Relids left_relids, Relids right_relids); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + int partidx); /* @@ -1379,6 +1383,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (IS_SIMPLE_REL(rel1) && child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts); + if (IS_SIMPLE_REL(rel1) && child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts); + /* We should never try to join two overlapping sets of rels. */ Assert(!bms_overlap(child_rel1->relids, child_rel2->relids)); child_joinrelids = bms_union(child_rel1->relids, child_rel2->relids); @@ -1627,3 +1636,55 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +static RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx) +{ + RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + AppendRelInfo *appinfo = make_append_rel_info(parent, parentrte, + parent->tupdesc, + parentrte->relid, + parent->reltype, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index a66374094f..c628f3d9e7 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -616,64 +616,6 @@ create_lateral_join_info(PlannerInfo *root) bms_add_member(brel2->lateral_referencers, rti); } } - - /* - * Lastly, propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - */ - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - RangeTblEntry *brte = root->simple_rte_array[rti]; - - /* - * Skip empty slots. Also skip non-simple relations i.e. dead - * relations. - */ - if (brel == NULL || !IS_SIMPLE_REL(brel)) - continue; - - /* - * In the case of table inheritance, the parent RTE is directly linked - * to every child table via an AppendRelInfo. In the case of table - * partitioning, the inheritance hierarchy is expanded one level at a - * time rather than flattened. Therefore, an other member rel that is - * a partitioned table may have children of its own, and must - * therefore be marked with the appropriate lateral info so that those - * children eventually get marked also. - */ - Assert(brte); - if (brel->reloptkind == RELOPT_OTHER_MEMBER_REL && - (brte->rtekind != RTE_RELATION || - brte->relkind != RELKIND_PARTITIONED_TABLE)) - continue; - - if (brte->inh) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); - RelOptInfo *childrel; - - if (appinfo->parent_relid != rti) - continue; - childrel = root->simple_rel_array[appinfo->child_relid]; - Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - Assert(childrel->direct_lateral_relids == NULL); - childrel->direct_lateral_relids = brel->direct_lateral_relids; - Assert(childrel->lateral_relids == NULL); - childrel->lateral_relids = brel->lateral_relids; - Assert(childrel->lateral_referencers == NULL); - childrel->lateral_referencers = brel->lateral_referencers; - } - } - } } diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 78557aeecb..62e11157da 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -254,6 +254,16 @@ query_planner(PlannerInfo *root, List *tlist, } /* + * Expand RT entries that represent inherited or partitioned tables. + * This will perform partition pruning on partitioned tables in the + * original range table and also recursively on any child partitioned + * tables that were added by the expansion of the original parent(s). + * As new entries are added to the range table, various arrays in the + * PlannerInfo will be expanded accordingly. + */ + expand_inherited_tables(root); + + /* * Add child subroots needed to use during planning for individual child * targets */ diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 703babfdf0..f80f35eed3 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -25,6 +25,7 @@ #include "access/sysattr.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -704,27 +705,18 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } /* - * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). - */ - preprocess_rowmarks(root); - - /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain base relations not joins, so it's OK (and marginally more - * efficient) to do it after checking for join RTEs. We must do it after - * pulling up subqueries, else we'd fail to handle inherited tables in - * subqueries. - */ - expand_inherited_tables(root); - - /* * Now that we have figured out "actual" inheritance situation of the * relations, set whether the query is an inherited UPDATE/DELETE. */ @@ -732,6 +724,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh); /* + * Preprocess RowMark information. We need to do this after subquery + * pullup (so that all non-inherited RTEs are present). + */ + preprocess_rowmarks(root); + + /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -1233,7 +1231,9 @@ inheritance_planner(PlannerInfo *root) * of the parent. */ root->unexpanded_tlist = list_copy(root->parse->targetList); - tlist = preprocess_targetlist(root); + + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root, false); root->processed_tlist = tlist; qp_extra.tlist = tlist; qp_extra.activeWindows = NIL; @@ -1243,9 +1243,11 @@ inheritance_planner(PlannerInfo *root) /* * If it turned out during query planning that all the children are dummy * (pruned or excluded by constraints), no need to do the steps below. - * Let grouping_planner finish up the final path. + * For regular inheritance case, it's possible that we only need to modify + * the parent table. Let grouping_planner finish up the final path. */ - if (IS_DUMMY_REL(planned_rel)) + parent_rte = planner_rt_fetch(top_parentRTindex, root); + if (!parent_rte->inh || IS_DUMMY_REL(planned_rel)) { grouping_planner(root, false, planned_rel, 0.0); return; @@ -1261,7 +1263,6 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = planner_rt_fetch(top_parentRTindex, root); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1590,14 +1591,19 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, */ if (planned_rel == NULL) { - tlist = preprocess_targetlist(root); + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root, false); /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation - * so that we can transfer its decoration (resnames etc) to the - * topmost tlist of the finished Plan. + * We are now mostly done hacking up the query's targetlist. More + * columns might be added during query_planner after inheritance + * expansion in make_one_rel, because some children may require + * different column than the parent for row mark handling; see + * add_rowmark_junk_columns(). Most of the remaining planning + * work will be done with the PathTarget representation of tlists, + * but save aside the full representation so that we can transfer + * its decoration (resnames etc) to the topmost tlist of the + * finished Plan. */ root->processed_tlist = tlist; } @@ -2373,7 +2379,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2398,7 +2404,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } @@ -6835,6 +6841,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -6936,6 +6946,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 5d363edab8..4dcdede6b6 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -325,6 +325,12 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) UPPERREL_FINAL, NULL))) add_rtes_to_flat_rtable(rel->subroot, true); } + /* + * A NULL rel also means an unplanned subquery rte, so apply + * flatten_unplanned_rtes. + */ + else + flatten_unplanned_rtes(glob, rte); } rti++; } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index f8bc2dd257..a55949c305 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -65,9 +65,13 @@ static List *expand_targetlist(List *tlist, int command_type, * * As a side effect, if there's an ON CONFLICT UPDATE clause, its targetlist * is also preprocessed (and updated in-place). + * + * inheritance_expanded specifies whether inheritance tables are expanded + * as of calling this function. Caller must specify it because that affects + * which row marking related junk columns get added to the targetlist. */ List * -preprocess_targetlist(PlannerInfo *root) +preprocess_targetlist(PlannerInfo *root, bool inheritance_expanded) { Query *parse = root->parse; int result_relation = parse->resultRelation; @@ -134,6 +138,15 @@ preprocess_targetlist(PlannerInfo *root) if (rc->rti != rc->prti) continue; + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which will change the parent row + * mark's allMarkTypes fields. + */ + if (rc->isParent && !inheritance_expanded) + continue; + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) { /* Need to fetch TID */ diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 55eeb5127c..d4d36248a1 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -31,13 +31,16 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" +#include "optimizer/var.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" #include "utils/lsyscache.h" diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index 34a3ebbbe4..d2c3c7b590 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -31,10 +31,10 @@ typedef struct AppendRelInfo **appinfos; } adjust_appendrel_attrs_context; -static void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, adjust_appendrel_attrs_context *context); static List *adjust_inherited_tlist(List *tlist, @@ -46,18 +46,20 @@ static List *adjust_inherited_tlist(List *tlist, * Build an AppendRelInfo for the parent-child pair */ AppendRelInfo * -make_append_rel_info(Relation parentrel, Relation childrel, - Index parentRTindex, Index childRTindex) +make_append_rel_info(RelOptInfo *parent, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childtype, + Index childRTindex) { AppendRelInfo *appinfo = makeNode(AppendRelInfo); - appinfo->parent_relid = parentRTindex; + appinfo->parent_relid = parent->relid; appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->rd_rel->reltype; - appinfo->child_reltype = childrel->rd_rel->reltype; - make_inh_translation_list(parentrel, childrel, childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = RelationGetRelid(parentrel); + appinfo->parent_reltype = parent->reltype; + appinfo->child_reltype = childtype; + make_inh_translation_list(parent->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; return appinfo; } @@ -70,14 +72,11 @@ make_append_rel_info(Relation parentrel, Relation childrel, * For paranoia's sake, we match type/collation as well as attribute name. */ static void -make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, - List **translated_vars) +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) { List *vars = NIL; - TupleDesc old_tupdesc = RelationGetDescr(oldrelation); - TupleDesc new_tupdesc = RelationGetDescr(newrelation); - Oid new_relid = RelationGetRelid(newrelation); int oldnatts = old_tupdesc->natts; int newnatts = new_tupdesc->natts; int old_attno; @@ -107,7 +106,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, * When we are generating the "translation list" for the parent table * of an inheritance set, no need to search for matches. */ - if (oldrelation == newrelation) + if (from_rel == to_rel) { vars = lappend(vars, makeVar(newvarno, (AttrNumber) (old_attno + 1), @@ -133,10 +132,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, { HeapTuple newtup; - newtup = SearchSysCacheAttName(new_relid, attname); + newtup = SearchSysCacheAttName(to_rel, attname); if (!newtup) elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; ReleaseSysCache(newtup); @@ -146,10 +145,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, /* Found it, check type and collation match */ if (atttypid != att->atttypid || atttypmod != att->atttypmod) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); if (attcollation != att->attcollation) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); vars = lappend(vars, makeVar(newvarno, (AttrNumber) (new_attno + 1), diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 0d2b42f72e..f1bd954373 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -18,33 +18,43 @@ #include "access/sysattr.h" #include "catalog/partition.h" #include "catalog/pg_inherits.h" +#include "catalog/pg_type.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" #include "optimizer/inherit.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" +#include "optimizer/var.h" +#include "partitioning/partprune.h" #include "utils/rel.h" static PlannerInfo *create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo); -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); +static void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti); +static void expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, + RangeTblEntry *rte, Index rti, RelOptInfo *rel); static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); - +static RelOptInfo *build_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); /* * expand_inherited_tables @@ -52,37 +62,145 @@ static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, * into an "append relation". At the conclusion of this process, * the "inh" flag is set in all and only those RTEs that are append * relation parents. + * + * Note that although we're calling the combined output of inheritance set + * relations an "append relation" here, the caller may not always want to + * combine the relations. For example, if the parent of the inheritance + * set is the query's target relation, each child relation is processed + * on its own as the query's target relation. */ void expand_inherited_tables(PlannerInfo *root) { - Index nrtes; + int orig_rtable_size; Index rti; - ListCell *rl; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; /* * expand_inherited_rtentry may add RTEs to parse->rtable. The function is * expected to recursively handle any RTEs that it creates with inh=true. * So just scan as far as the original end of the rtable list. */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) + for (rti = 1; rti < orig_rtable_size; rti++) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + if (rte->inh) + expand_inherited_rtentry(root, brel, rte, rti); } } /* * expand_inherited_rtentry - * Check whether a rangetable entry represents an inheritance set. - * If so, add entries for all the child tables to the query's - * rangetable, and build AppendRelInfo nodes for all the child tables - * and add them to root->append_rel_list. If not, clear the entry's - * "inh" flag to prevent later code from looking for AppendRelInfos. + * This initializes RelOptInfos for inheritance child relations if the + * passed-in relation has any + * + * 'rel' is the parent relation, whose range table entry ('rte') has been + * marked to require adding children. Parent could either be a subquery (if + * we flattened UNION ALL query) or a table that's known to have (or once had) + * inheritance children. The latter consists of both regular inheritance + * parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as a child to be scanned as part of the + * inheritance set. The original RTE is considered to represent the whole + * inheritance set. + */ +static void +expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_inheritance_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_nonpartitioned_inherited_rtentry(root, rte, rti, rel); + } +} + +/* + * expand_nonpartitioned_inherited_rtentry + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. * * Note that the original RTE is considered to represent the whole * inheritance set. The first of the generated RTEs is an RTE for the same @@ -93,55 +211,35 @@ expand_inherited_tables(PlannerInfo *root) * regular inheritance, a parent RTE must always have at least two associated * AppendRelInfos: one corresponding to the parent table as a simple member of * inheritance set and one or more corresponding to the actual children. - * Since a partitioned table is not scanned, it might have only one associated - * AppendRelInfo. */ static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, + RangeTblEntry *rte, + Index rti, + RelOptInfo *rel) { Oid parentOID; PlanRowMark *oldrc; - Relation oldrelation; - LOCKMODE lockmode; + LOCKMODE lockmode = rte->rellockmode; List *inhOIDs; ListCell *l; + int num_children; + int num_children_added = 0; - /* Does RT entry allow inheritance? */ - if (!rte->inh) - return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } - /* Fast path for common case of childless table */ + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); parentOID = rte->relid; - if (!has_subclass(parentOID)) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * The rewriter should already have obtained an appropriate lock on each - * relation named in the query. However, for each child relation we add - * to the query, we must obtain an appropriate lock, because this will be - * the first use of those relations in the parse/rewrite/plan pipeline. - * Child rels should use the same lockmode as their parent. - */ - lockmode = rte->rellockmode; /* Scan for all members of inheritance set, acquire needed locks */ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); /* * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite above has_subclass() check, if table - * once had a child but no longer does. + * case. This could happen despite has_subclass() check performed by + * subquery_planner, if table once had a child but no longer does. */ - if (list_length(inhOIDs) < 2) + num_children = list_length(inhOIDs); + if (num_children < 2) { /* Clear flag before returning */ rte->inh = false; @@ -149,216 +247,221 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } /* - * If parent relation is selected FOR UPDATE/SHARE, we need to mark its - * PlanRowMark as isParent = true, and generate a new PlanRowMark for each - * child. + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. */ oldrc = get_plan_rowmark(root->rowMarks, rti); - if (oldrc) - oldrc->isParent = true; + Assert(oldrc == NULL || oldrc->isParent); /* - * Must open the parent relation to examine its tupdesc. We need not lock - * it; we assume the rewriter already did. + * Must expand PlannerInfo arrays by num_children before we can add + * children. */ - oldrelation = heap_open(parentOID, NoLock); + Assert(num_children > 0); + expand_planner_arrays(root, num_children); - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) + foreach(l, inhOIDs) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); - - /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. - */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); - } - else - { - List *appinfos = NIL; + Oid childOID = lfirst_oid(l); + Relation newrelation; RangeTblEntry *childrte; Index childRTindex; + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. */ - foreach(l, inhOIDs) + if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = heap_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - heap_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - heap_close(newrelation, NoLock); + heap_close(newrelation, lockmode); + continue; } - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); + (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation, &childrte, + &childRTindex); + Assert(childrte != NULL); + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + Assert(childRTindex > 0); + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; } - heap_close(oldrelation, NoLock); + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } } /* * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) + Index parentRTindex, RelOptInfo *parentrel) { + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; - check_stack_depth(); - - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + if (bms_num_members(partindexes) > 0) + expand_planner_arrays(root, bms_num_members(partindexes)); /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. */ - if (partdesc->nparts == 0) - { - parentrte->inh = false; - return; - } + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); - for (i = 0; i < partdesc->nparts; i++) + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) { - Oid childOID = partdesc->oids[i]; - Relation childrel; + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + RangeTblEntry *childrte; + Index childRTindex; - /* Open rel; we already have required locks */ - childrel = heap_open(childOID, NoLock); + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation, + &childrte, &childRTindex); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + + /* If the child is partitioned itself, expand it too. */ + if (childrel->part_scheme) + { + Assert(childrte->inh); expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); + childrel); + } + } - /* Close child relation, but keep locks */ - heap_close(childrel, NoLock); + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); } } /* - * expand_single_inheritance_child - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) + * The return value is the RelOptInfo that's added. * * PlanRowMarks still carry the top-parent's RTI, and the top-parent's * allMarkTypes field still accumulates values from all descendents. * * "parentrte" and "parentRTindex" are immediate parent's RTE and * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. */ -static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); Oid childOID = RelationGetRelid(childrel); RangeTblEntry *childrte; Index childRTindex; AppendRelInfo *appinfo; + RelOptInfo *childrelopt; /* * Build an RTE for the child, and attach to query's rangetable list. We * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh = false. Also, set requiredPerms to zero since - * all required permissions checks are done on the original RTE. Likewise, - * set the child's securityQuals to empty, because we only want to apply - * the parent's RLS conditions regardless of what RLS properties + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties * individual children may have. (This is an intentional choice to make * inherited RLS work like regular permissions checks.) The parent * securityQuals will be propagated to children along with other base @@ -368,49 +471,42 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childrte_p = childrte; childrte->relid = childOID; childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) - childrte->inh = true; - else - childrte->inh = false; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); childrte->requiredPerms = 0; childrte->securityQuals = NIL; parse->rtable = lappend(parse->rtable, childrte); childRTindex = list_length(parse->rtable); *childRTindex_p = childRTindex; - /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. - */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) - { - appinfo = make_append_rel_info(parentrel, childrel, - parentRTindex, childRTindex); - *appinfos = lappend(*appinfos, appinfo); + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + RelationGetDescr(childrel), + RelationGetRelid(childrel), + RelationGetForm(childrel)->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); } /* @@ -442,6 +538,253 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need to create one, + * because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_inheritance_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; +} + +/* + * build_inheritance_child_rel + * Build a RelOptInfo for child relation of an inheritance set + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields based on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turn out to be self-contradictory, + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + List *childquals; + ListCell *lc; + bool have_const_false_cq; + Index cq_min_security; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = false; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, parent->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual = (Node *) rinfo->clause; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, childqual, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might + * have securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals.) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except + * that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* + * not likely that we'd see constants here, so no + * check + */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, + false, + false, + security_level, + NULL, NULL, + NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* Set child's version of baserestrictinfo. */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; } /* @@ -583,7 +926,7 @@ create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) * column, filling placeholder entries for dropped columns, etc., all of * which occurs with the child's TupleDesc. */ - tlist = preprocess_targetlist(subroot); + tlist = preprocess_targetlist(subroot, true); subroot->processed_tlist = tlist; /* Add any newly added Vars to the child RelOptInfo. */ diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 6009de309e..293f9ac619 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -105,20 +105,20 @@ static void set_baserel_partition_key_exprs(Relation relation, * important for it. */ void -get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) +get_relation_info(PlannerInfo *root, RangeTblEntry *rte, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; bool hasindex; List *indexinfos = NIL; + bool inhparent = rte->inh; /* * We need not lock the relation since it was already locked, either by * the rewriter or when expand_inherited_rtentry() added it to the query's * rangetable. */ - relation = heap_open(relationObjectId, NoLock); + relation = heap_open(rte->relid, NoLock); /* Temporary and unlogged relations are inaccessible during recovery. */ if (!RelationNeedsWAL(relation) && RecoveryInProgress()) @@ -444,11 +444,32 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, get_relation_foreign_keys(root, rel, relation, inhparent); /* - * Collect info about relation's partitioning scheme, if any. Only - * inheritance parents may be partitioned. + * Collect some additional information for inheritance parents. */ - if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - set_relation_partition_info(root, rel, relation); + if (inhparent) + { + /* + * We'll need the TupleDesc when initializing the child relation. + * A copy is being made because concurrent changes might drop + * the relcache entry. That's possible because ALTER TABLE + * child_table NO INHERIT parent_table only requires an + * AccessShareLock on parent_table. + */ + rel->tupdesc = CreateTupleDescCopy(RelationGetDescr(relation)); + rel->reltype = RelationGetForm(relation)->reltype; + + /* + * If partitioned, also save the information of partitioning scheme, + * and whether the query updates any of the partition key columns. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + set_relation_partition_info(root, rel, relation); + root->partColsUpdated |= has_partition_attrs(relation, + rte->updatedCols, + NULL); + } + } heap_close(relation, NoLock); @@ -458,7 +479,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * removing an index, or adding a hypothetical index to the indexlist. */ if (get_relation_info_hook) - (*get_relation_info_hook) (root, relationObjectId, inhparent, rel); + (*get_relation_info_hook) (root, rte->relid, rte->inh, rel); } /* @@ -1844,16 +1865,20 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; + + /* + * Since we must've taken a lock on the table, it's okay to simply copy + * the pointers to relcache data here. + */ + rel->part_oids = partdesc->oids; + rel->boundinfo = partdesc->boundinfo; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index fe83ec4519..d12d047002 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -132,6 +132,50 @@ setup_append_rel_array(PlannerInfo *root) } /* + * expand_planner_arrays + * Expand the PlannerInfo arrays by add_size members and initialize the + * the newly added bytes with zero + */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size; + + Assert(add_size > 0); + + new_size = root->simple_rel_array_size + add_size; + + /* Expand various arrays and 0-initialize added bytes. */ + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + + if (root->append_rel_array) + { + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + } + else + { + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + } + + root->simple_rel_array_size = new_size; +} + +/* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. */ @@ -195,6 +239,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->joininfo = NIL; rel->has_eclass_joins = false; rel->consider_partitionwise_join = false; /* might get changed later */ + rel->top_parent_relids = NULL; /* might be changed later */ rel->part_scheme = NULL; rel->nparts = 0; rel->boundinfo = NULL; @@ -203,6 +248,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->partexprs = NULL; rel->nullable_partexprs = NULL; rel->partitioned_child_rels = NIL; + rel->inh_root_parent = 0; /* might be changed later */ /* * Pass top parent's relids down the inheritance hierarchy. If the parent @@ -216,16 +262,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->top_parent_relids = parent->top_parent_relids; else rel->top_parent_relids = bms_copy(parent->relids); + + /* + * For inheritance child relations, we also set inh_root_parent. + * Note that 'parent' might itself be a child (a sub-partitioned + * partition), in which case we simply use its value of + * inh_root_parent. + */ + if (parent->rtekind == RTE_RELATION) + rel->inh_root_parent = parent->inh_root_parent > 0 ? + parent->inh_root_parent : + parent->relid; } - else - rel->top_parent_relids = NULL; /* Check type of rtable entry */ switch (rte->rtekind) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte, rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -266,52 +321,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); - /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. - */ - if (rte->inh) - { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); - - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; - } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); - } - return rel; } diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 901433c68c..9bb472fd98 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -46,6 +46,7 @@ #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" #include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/planner.h" #include "optimizer/predtest.h" @@ -437,17 +438,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -541,23 +548,20 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ @@ -565,6 +569,13 @@ prune_append_rel_partitions(RelOptInfo *rel) return NULL; /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. */ @@ -591,15 +602,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index beb2e323a8..01a664d246 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -15,6 +15,7 @@ #define RELATION_H #include "access/sdir.h" +#include "access/tupdesc.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/params.h" @@ -729,11 +730,28 @@ typedef struct RelOptInfo int nparts; /* number of partitions */ struct PartitionBoundInfoData *boundinfo; /* Partition bounds */ List *partition_qual; /* partition constraint */ + Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ + + /* + * For inheritance children, this is the RT index of inheritance table + * mentioned in the query from which this relation originated. + * top_parent_relids cannot be used for this, because if the inheritance + * root table is itself under UNION ALL, top_parent_relids contains the + * RT index of UNION ALL parent subquery. + */ + Index inh_root_parent; + + /* + * Set only if this is an inheritance parent relation. This information + * is needed when initializing the planning info for children. + */ + TupleDesc tupdesc; /* A "copy" of the table's tuple desriptor */ + Oid reltype; /* Table's reltype */ } RelOptInfo; /* diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h index 604e36d73c..8f7b06a608 100644 --- a/src/include/optimizer/appendinfo.h +++ b/src/include/optimizer/appendinfo.h @@ -17,9 +17,10 @@ #include "nodes/relation.h" #include "utils/relcache.h" -extern AppendRelInfo *make_append_rel_info(Relation parentrel, - Relation childrel, - Index parentRTindex, Index childRTindex); +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parent, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childtype, + Index childRTindex); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index bd905d3328..e7d0a20c54 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -261,6 +261,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index a1b23251a1..d8091cdfa4 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -25,8 +25,8 @@ typedef void (*get_relation_info_hook_type) (PlannerInfo *root, extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; -extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); +extern void get_relation_info(PlannerInfo *root, RangeTblEntry *rte, + RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 62d45dd142..a329e87fc2 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -38,7 +38,8 @@ extern Expr *canonicalize_qual(Expr *qual, bool is_check); /* * prototypes for preptlist.c */ -extern List *preprocess_targetlist(PlannerInfo *root); +extern List *preprocess_targetlist(PlannerInfo *root, + bool inheritance_expanded); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h index ce9bc8d9fd..5c5d27d040 100644 --- a/src/include/partitioning/partprune.h +++ b/src/include/partitioning/partprune.h @@ -76,7 +76,7 @@ extern PartitionPruneInfo *make_partition_pruneinfo(PlannerInfo *root, List *subpaths, List *partitioned_rels, List *prunequal); -extern Relids prune_append_rel_partitions(RelOptInfo *rel); +extern Bitmapset *prune_append_rel_partitions(RelOptInfo *rel); extern Bitmapset *get_matching_partitions(PartitionPruneContext *context, List *pruning_steps); diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 6bc106831e..1450cef057 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) -- 2.11.0
v15-0003-Teach-planner-to-only-process-unpruned-partition.patch (text/plain, 6.8K) download From ca5a541d56177bc2cf2abd526cf3042b7169bf23 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 15 Oct 2018 10:59:24 +0900 Subject: [PATCH v15 3/4] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/inherit.c | 1 + src/backend/optimizer/util/relnode.c | 3 +++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/relation.h | 4 ++++ 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 75edac96f4..97b79035f0 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1426,6 +1426,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index f80f35eed3..faf5f343fb 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6833,7 +6833,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -6841,9 +6843,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -6924,7 +6924,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -6936,7 +6935,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -6946,9 +6947,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index f1bd954373..d6f120fcc1 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -361,6 +361,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, /* Perform pruning. */ partindexes = prune_append_rel_partitions(parentrel); + parentrel->live_parts = partindexes; /* Must expand PlannerInfo arrays before we can add children. */ if (bms_num_members(partindexes) > 0) diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index d12d047002..059ab8f35e 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1757,6 +1757,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 9bb472fd98..088b191bdd 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -441,29 +441,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 01a664d246..d27611c2c7 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -733,6 +733,10 @@ typedef struct RelOptInfo Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Indexes into part_rels of the non-NULL + * RelOptInfos of unpruned partitions; exists + * to avoid having to iterate over the entire + * part_rels array to filter NULL entries. */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.11.0
v15-0004-Do-not-lock-all-partitions-at-the-beginning.patch (text/plain, 1.7K) download From e0c030502e531b9c5cd7949498251670c3673146 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 12 Oct 2018 19:12:53 +0900 Subject: [PATCH v15 4/4] Do not lock all partitions at the beginning --- src/backend/optimizer/util/inherit.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index d6f120fcc1..426d64faea 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -328,10 +328,6 @@ expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, * * Partitions are added to the query in order in which they are found in * the parent's PartitionDesc. - * - * Note: even though only the unpruned partitions will be added to the - * resulting plan, this still locks *all* partitions via find_all_inheritors - * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, @@ -345,10 +341,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, parentrel->inh_root_parent : parentRTindex; - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - /* * Initialize partitioned_child_rels to contain this RT index. * @@ -385,8 +377,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, RangeTblEntry *childrte; Index childRTindex; - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); + newrelation = heap_open(childOID, lockmode); Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-17 10:24  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  0 siblings, 2 replies; 265+ messages in thread

From: Amit Langote @ 2019-01-17 10:24 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Thank you Imai-san for testing.  Sorry it totally slipped my mind to reply
to this email.

On 2019/01/09 11:08, Imai, Yoshikazu wrote:
> I wonder why force_custom_plan is faster than auto after applied the patch.
> 
> When we use PREPARE-EXECUTE, a generic plan is created and used if its cost is
> cheaper than creating and using a custom plan with plan_cache_mode='auto',
> while a custom plan is always created and used with plan_cache_mode='force_custom_plan'.
> So one can think the difference in above results is because of creating or
> using a generic plan.
> 
> I checked how many times a generic plan is created during executing pgbench and
> found a generic plan is created only once and custom plans are created at other
> times with plan_cache_mode='auto'. I also checked the time of creating a
> generic plan, but it didn't take so much(250ms or so with 4096 partitions). So
> the time of creating a generic plan does not affect the performance.
> 
> Currently, a generic plan is created at sixth time of executing EXECUTE query.
> I changed it to more later (ex. at 400,000th time of executing EXECUTE query on
> master with 4096 partitions, because 7000TPS x 60sec=420,0000 transactions are
> run while executing pgbench.), then there are almost no difference between auto
> and force_custom_plan. I think that creation of a generic plan affects the time
> of executing queries which are ordered after creating generic plan.
> 
> If my assumption is right, we can expect some additional process is occurred at
> executing queries ordered after creating a generic plan, which results in auto is
> slower than force_custom_plan because of additional process. But looking at
> above results, on master with 4096 partitions, auto is faster than force_custom_plan.
> So now I am confused.
> 
> Do you have any ideas what does affect the performance?

Are you saying that, when using auto mode, all executions of the query
starting from 7th are slower than the first 5 executions?  That is, the
latency of creating and using a custom plan increases *after* a generic
plan is created and discarded on the 6th execution of the query?  If so,
that is inexplicable to me.

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-01-18 05:12  Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 1 reply; 265+ messages in thread

From: Tsunakawa, Takayuki @ 2019-01-18 05:12 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Imai-san,

From: Amit Langote [mailto:Langote_Amit_f8@lab.ntt.co.jp]
> On 2019/01/09 11:08, Imai, Yoshikazu wrote:
> > I wonder why force_custom_plan is faster than auto after applied the patch.
> >
> > When we use PREPARE-EXECUTE, a generic plan is created and used if its
> cost is
> > cheaper than creating and using a custom plan with plan_cache_mode='auto',
> > while a custom plan is always created and used with
> plan_cache_mode='force_custom_plan'.
> > So one can think the difference in above results is because of creating
> or
> > using a generic plan.
> >
> > I checked how many times a generic plan is created during executing pgbench
> and
> > found a generic plan is created only once and custom plans are created
> at other
> > times with plan_cache_mode='auto'. I also checked the time of creating
> a
> > generic plan, but it didn't take so much(250ms or so with 4096 partitions).
> So
> > the time of creating a generic plan does not affect the performance.
> >
> > Currently, a generic plan is created at sixth time of executing EXECUTE
> query.
> > I changed it to more later (ex. at 400,000th time of executing EXECUTE
> query on
> > master with 4096 partitions, because 7000TPS x 60sec=420,0000
> transactions are
> > run while executing pgbench.), then there are almost no difference between
> auto
> > and force_custom_plan. I think that creation of a generic plan affects
> the time
> > of executing queries which are ordered after creating generic plan.
> >
> > If my assumption is right, we can expect some additional process is
> occurred at
> > executing queries ordered after creating a generic plan, which results
> in auto is
> > slower than force_custom_plan because of additional process. But looking
> at
> > above results, on master with 4096 partitions, auto is faster than
> force_custom_plan.
> > So now I am confused.
> >
> > Do you have any ideas what does affect the performance?
> 
> Are you saying that, when using auto mode, all executions of the query
> starting from 7th are slower than the first 5 executions?  That is, the
> latency of creating and using a custom plan increases *after* a generic
> plan is created and discarded on the 6th execution of the query?  If so,
> that is inexplicable to me.

Isn't CheckCachedPlan() (and AcquireExecutorLocks() therein) called in every EXECUTE after 6th one due to some unknow issue?
Does choose_custom_plan() always return true after 6th EXECUTE?

Regards
Takayuki Tsunakawa




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-18 05:28  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
  0 siblings, 1 reply; 265+ messages in thread

From: Amit Langote @ 2019-01-18 05:28 UTC (permalink / raw)
  To: Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Tsunakawa-san,

On 2019/01/18 14:12, Tsunakawa, Takayuki wrote:
> From: Amit Langote [mailto:Langote_Amit_f8@lab.ntt.co.jp]
>> Are you saying that, when using auto mode, all executions of the query
>> starting from 7th are slower than the first 5 executions?  That is, the
>> latency of creating and using a custom plan increases *after* a generic
>> plan is created and discarded on the 6th execution of the query?  If so,
>> that is inexplicable to me.
> 
> Isn't CheckCachedPlan() (and AcquireExecutorLocks() therein) called in every EXECUTE after 6th one due to some unknow issue?

CheckCachedPlan is only called if choose_custom_plan() returns false
resulting in generic plan being created/reused.  With plan_cache_mode =
auto, I see it always returns true, because a custom plan containing a
single partition to scan is way cheaper than the generic plan.

> Does choose_custom_plan() always return true after 6th EXECUTE?

Yes.

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-01-21 00:45  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 2 replies; 265+ messages in thread

From: Imai, Yoshikazu @ 2019-01-21 00:45 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Hi Amit-san,

On Wed, Jan 17, 2019 at 10:25 AM, Amit Langote wrote:
> Thank you Imai-san for testing.  Sorry it totally slipped my mind to reply to this email.

Thanks for replying and sorry for my late reply. I've been undertaking on-the-job training last week.

> Are you saying that, when using auto mode, all executions of the query
> starting from 7th are slower than the first 5 executions?  That is, the
> latency of creating and using a custom plan increases *after* a generic
> plan is created and discarded on the 6th execution of the query?  If so,
> that is inexplicable to me.

Yes. And it's also inexplicable to me.

I'll check if this fact is really correct by majoring the time of the
first 5 queries before generic plan is created and the other queries
after generic plan is created.

Yoshikazu Imai 



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-01-21 00:51  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 0 replies; 265+ messages in thread

From: Imai, Yoshikazu @ 2019-01-21 00:51 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Tsunakawa-san

On Thu, Jan 18, 2019 at 5:29 AM, Amit Langote wrote:
> On 2019/01/18 14:12, Tsunakawa, Takayuki wrote:
...
> > Isn't CheckCachedPlan() (and AcquireExecutorLocks() therein) called
> in every EXECUTE after 6th one due to some unknow issue?
> 
> CheckCachedPlan is only called if choose_custom_plan() returns false
> resulting in generic plan being created/reused.  With plan_cache_mode
> = auto, I see it always returns true, because a custom plan containing
> a single partition to scan is way cheaper than the generic plan.
> 
> > Does choose_custom_plan() always return true after 6th EXECUTE?
> 
> Yes.

Yes.
I also checked choose_custom_plan() always returns true excluding 6th EXECUTE
and CheckCachedPlan() is only called at 6th EXECUTE.

Yoshikazu Imai 



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-21 01:31  David Rowley <david.rowley@2ndquadrant.com>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  1 sibling, 1 reply; 265+ messages in thread

From: David Rowley @ 2019-01-21 01:31 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On Mon, 21 Jan 2019 at 13:45, Imai, Yoshikazu
<imai.yoshikazu@jp.fujitsu.com> wrote:
> On Wed, Jan 17, 2019 at 10:25 AM, Amit Langote wrote:
> > Are you saying that, when using auto mode, all executions of the query
> > starting from 7th are slower than the first 5 executions?  That is, the
> > latency of creating and using a custom plan increases *after* a generic
> > plan is created and discarded on the 6th execution of the query?  If so,
> > that is inexplicable to me.
>
> Yes. And it's also inexplicable to me.
>
> I'll check if this fact is really correct by majoring the time of the
> first 5 queries before generic plan is created and the other queries
> after generic plan is created.

It would be interesting to see the profiles of having the generic plan
being built on the 6th execution vs the 400,000th execution.

I'd thought maybe one difference would be the relcache hash table
having been expanded greatly after the generic plan was created, but I
see even the generic plan is selecting a random partition, so the
cache would have ended up with that many items eventually anyway, and
since we're talking in the odds of 7.8k TPS with 4k partitions, it
would have only have taken about 2-3 seconds out of the 60 second run
to hit most or all of those partitions anyway. So I doubt it could be
that.

-- 
 David Rowley                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-21 04:36  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  1 sibling, 0 replies; 265+ messages in thread

From: Amit Langote @ 2019-01-21 04:36 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Imai-san,

On 2019/01/21 9:45, Imai, Yoshikazu wrote:
> I'll check if this fact is really correct by majoring the time of the
> first 5 queries before generic plan is created and the other queries
> after generic plan is created.

That would really help.  If you are able to recreate that behavior
consistently, that might something to try to fix at some point.

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-21 09:01  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 2 replies; 265+ messages in thread

From: Amit Langote @ 2019-01-21 09:01 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Amit Langote <amitlangote09@gmail.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers

On 2019/01/17 11:17, Amit Langote wrote:
> I have rebased the patches.  Other than rebasing, I've squashed the patch
> to add inh_root_parent field to RelOptInfo which contained no other code
> changes to use that field into the patch that contains changes to start
> using it.  Updated the commit message of lazy-partition-initialization
> patch to be accurate (it contained old details that have changed since I
> first wrote that commit message).

Rebased again due to 8d8dcead1295.

Thanks,
Amit

Attachments: download all (4 files, .tar.gz)

v16-0001-Overhaul-inheritance-update-delete-planning.patch (text/plain, 75.6K) download From 3b0dfe4a3c1912ecb5ed9cc042b51ad1ccb09f59 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 11:18:12 +0900 Subject: [PATCH v16 1/4] Overhaul inheritance update/delete planning With the current method, inheritance_planner, which handles the planning for update/delete commands targeting inheritance trees, repeatedly calls query_planner for each child table. That's inefficient for two reasons: 1. partprune.c cannot be used to perform partition pruning, because it can only be invoked from query_planner. With the current approach, query_planner only sees the individual partitions in the query tree it receives, not the parent partitioned table. This leaves each partition to be pruned using constraint exclusion. (constraint exclusion cannon prune hash partitions which means UPDATE/DELETE don't support hash partition pruning.) 2. Repeated invocation of query_planner results in allocating large amounts of memory, especially if there are many child tables. Also, it involves repeatedly performing the same processing, such as jointree processing. This commit addresses both of the issues by modifying query_planner to handle the inheritance target case directly. Instead of having the caller create separate PlannerInfo's for individual child target relations and pass them to query_planner (actually, via grouping_planner), this teaches query_planner itself to create the child PlannerInfo's by the original parent PlannerInfo. It creates them *after* it has generated enough state in the latter, such as RelOptInfos for various relations, EquivalenceClasses after processing the join tree, and *before* generating the scan and join paths. Scan paths are generated normally, except that Paths of target children are not "appended". Join paths are generated separately for each child target relation, that is, by replacing the parent relation references in the original join tree with the child relation references. Joinrels (RelOptInfos thereof) corresponding to various child target relation are saved in the parent PlannerInfo for further processing. inheritance_planner now calls query_planner just once at the beginning with the original unmodified query, which takes care of generating top-level join rels for the child queries as described above. For each child join rel, it then calls grouping_planner using previously created child PlannerInfo to finish up the paths such that they produce query's top-level target list expanded according to a given child relation's descriptor. grouping_planner()'s interface is modified so that we can pass it what's called a 'planned_rel', a RelOptInfo that already contains the necessary paths needed to produce its output. This removes some existing code in inheritance_planner that dealt with any subquery RTEs in the query. The rationale of that code was that the subquery RTEs may change during each iteration of planning (that is, for different children), so different iterations better use different copies of those RTEs. That was handled by making fresh copies of those RTEs for each iteration of planning which were appended to the range table, accompanied by modifying all expressions and auxiliary structures that referenced the original subquery RTEs to instead reference the copies (that is, change the varnos). This copying meant we would end up adding S * N new entries to the original range table by the time we got to the last unpruned child, where S is the number of subquery RTEs in the original query and N the number of unpruned children. Since with the new code we perform planning just once, I think we don't need this special handling. Actually, there is a regression test output change due to no longer having copies of subquery RTEs (see the diff of partition_join.out file.) Also we no longer need the hack in relation_excluded_by_constraints() involving setting PlannerInfo.inhTargetKind to enable constraint exclusion for target child relation, which is no longer needed. Constraint exclusion now runs during query_planner step described above. Regression test output change in partition_join.out is due to the fact that we no longer create duplicates of subquery RTEs in child range tables. --- doc/src/sgml/ddl.sgml | 17 +- src/backend/nodes/outfuncs.c | 2 +- src/backend/optimizer/path/allpaths.c | 613 +++++++++++++++++++++++++-- src/backend/optimizer/path/equivclass.c | 57 ++- src/backend/optimizer/path/joinrels.c | 2 +- src/backend/optimizer/plan/createplan.c | 10 - src/backend/optimizer/plan/planmain.c | 48 ++- src/backend/optimizer/plan/planner.c | 444 ++++++------------- src/backend/optimizer/prep/prepjointree.c | 1 - src/backend/optimizer/util/appendinfo.c | 33 +- src/backend/optimizer/util/inherit.c | 109 +++++ src/backend/optimizer/util/plancat.c | 73 +--- src/include/nodes/relation.h | 37 +- src/include/optimizer/inherit.h | 1 + src/include/optimizer/paths.h | 3 +- src/test/regress/expected/partition_join.out | 4 +- 16 files changed, 988 insertions(+), 466 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 85e4358988..1bb5ca6b08 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -4439,24 +4439,11 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <note> <para> - Currently, pruning of partitions during the planning of an - <command>UPDATE</command> or <command>DELETE</command> command is - implemented using the constraint exclusion method (however, it is - controlled by the <literal>enable_partition_pruning</literal> rather than - <literal>constraint_exclusion</literal>) &mdash; see the following section - for details and caveats that apply. - </para> - - <para> Execution-time partition pruning currently only occurs for the <literal>Append</literal> and <literal>MergeAppend</literal> node types. It is not yet implemented for the <literal>ModifyTable</literal> node - type. - </para> - - <para> - Both of these behaviors are likely to be changed in a future release - of <productname>PostgreSQL</productname>. + type, but that is likely to be changed in a future release of + <productname>PostgreSQL</productname>. </para> </note> </sect2> diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 0fde876c77..158da68925 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2210,7 +2210,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); WRITE_UINT_FIELD(qual_security_level); - WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind); WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasDeletedRTEs); @@ -2221,6 +2220,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_BITMAPSET_FIELD(curOuterRels); WRITE_NODE_FIELD(curOuterParams); WRITE_BOOL_FIELD(partColsUpdated); + WRITE_BOOL_FIELD(inherited_update); } static void diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index e1ecfd6052..61df22953a 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -38,6 +38,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" @@ -92,8 +93,12 @@ static void set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); +static void set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte); static void set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); +static void set_inherit_target_rel_pathlists(PlannerInfo *root, + RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, @@ -120,6 +125,8 @@ static void set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); +static void inheritance_make_rel_from_joinlist(PlannerInfo *root, + List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, pushdown_safety_info *safetyInfo); static bool recurse_pushdown_safe(Node *setOp, Query *topquery, @@ -150,27 +157,6 @@ make_one_rel(PlannerInfo *root, List *joinlist) Index rti; double total_pages; - /* - * Construct the all_baserels Relids set. - */ - root->all_baserels = NULL; - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - - /* there may be empty slots corresponding to non-baserel RTEs */ - if (brel == NULL) - continue; - - Assert(brel->relid == rti); /* sanity check on array */ - - /* ignore RTEs that are "other rels" */ - if (brel->reloptkind != RELOPT_BASEREL) - continue; - - root->all_baserels = bms_add_member(root->all_baserels, brel->relid); - } - /* Mark base rels as to whether we care about fast-start plans */ set_base_rel_consider_startup(root); @@ -218,13 +204,34 @@ make_one_rel(PlannerInfo *root, List *joinlist) /* * Generate access paths for the entire join tree. + * + * For UPDATE/DELETE on an inheritance parent, join paths should be + * generated for each child result rel separately. */ - rel = make_rel_from_joinlist(root, joinlist); + if (root->parse->resultRelation && + root->simple_rte_array[root->parse->resultRelation]->inh) + { + inheritance_make_rel_from_joinlist(root, joinlist); - /* - * The result should join all and only the query's base rels. - */ - Assert(bms_equal(rel->relids, root->all_baserels)); + /* + * Return the RelOptInfo of original target relation, although this + * doesn't really contain the final path. inheritance_planner + * from where we got here will generate the final path, but it will + * do so by iterative over child subroots, not through this + * RelOptInfo. + */ + rel = find_base_rel(root, root->parse->resultRelation); + } + else + { + rel = make_rel_from_joinlist(root, joinlist); + + /* + * The result should join all and only the query's base rels. + */ + Assert(bms_equal(rel->relids, root->all_baserels)); + + } return rel; } @@ -308,9 +315,10 @@ set_base_rel_sizes(PlannerInfo *root) * If parallelism is allowable for this query in general, see whether * it's allowable for this rel in particular. We have to do this * before set_rel_size(), because (a) if this rel is an inheritance - * parent, set_append_rel_size() will use and perhaps change the rel's - * consider_parallel flag, and (b) for some RTE types, set_rel_size() - * goes ahead and makes paths immediately. + * parent, set_append_rel_size() or set_inherit_target_rel_sizes() + * will use and perhaps change the rel's consider_parallel flag, and + * (b) for some RTE types, set_rel_size() goes ahead and makes paths + * immediately. */ if (root->glob->parallelModeOK) set_rel_consider_parallel(root, rel, rte); @@ -374,8 +382,15 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_size(root, rel, rti, rte); + /* + * If it's a target relation, set the sizes of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (rti == root->parse->resultRelation) + set_inherit_target_rel_sizes(root, rel, rti, rte); + else + set_append_rel_size(root, rel, rti, rte); } else { @@ -459,14 +474,26 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { + bool inherited_update = false; + if (IS_DUMMY_REL(rel)) { /* We already proved the relation empty, so nothing more to do */ } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_pathlist(root, rel, rti, rte); + /* + * If it's a target relation, set the pathlists of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (root->inherited_update && root->parse->resultRelation == rti) + { + inherited_update = true; + set_inherit_target_rel_pathlists(root, rel, rti, rte); + } + else + set_append_rel_pathlist(root, rel, rti, rte); } else { @@ -541,8 +568,12 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, if (set_rel_pathlist_hook) (*set_rel_pathlist_hook) (root, rel, rti, rte); - /* Now find the cheapest of the paths for this rel */ - set_cheapest(rel); + /* + * Now find the cheapest of the paths for this rel, unless it's an + * inheritance parent and this is an update/delete operation. + */ + if (!inherited_update) + set_cheapest(rel); #ifdef OPTIMIZER_DEBUG debug_print_rel(root, rel); @@ -904,6 +935,382 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) } /* + * set_inherit_target_rel_sizes + * Set size estimates for the child target relations + * + * The passed-in rel represents the target relation of the query that is + * known to have inheritance children. This is very much like + * set_append_rel_size, except it doesn't set the size estimates for the + * passed-in rel itself, because we don't need to "append" the children + * in this case. + */ +static void +set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + bool has_live_children; + ListCell *l; + Relids live_children = NULL; + bool did_pruning = false; + + /* Guard against stack overflow due to overly deep inheritance tree. */ + check_stack_depth(); + + Assert(IS_SIMPLE_REL(rel)); + + /* + * If the partitioned relation has any baserestrictinfo quals then we + * attempt to use these quals to prune away partitions that cannot + * possibly contain any tuples matching these quals. In this case we'll + * store the relids of all partitions which could possibly contain a + * matching tuple, and skip anything else in the loop below. + */ + if (enable_partition_pruning && + rte->relkind == RELKIND_PARTITIONED_TABLE && + rel->baserestrictinfo != NIL) + { + live_children = prune_append_rel_partitions(rel); + did_pruning = true; + } + + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + List *childquals; + Index cq_min_security; + bool have_const_false_cq; + PlannerInfo *subroot; + ListCell *lc; + List *translated_exprs, + *child_target_exprs; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + + /* + * The child rel's RelOptInfo was already created during + * add_base_rels_to_query. + */ + childrel = find_base_rel(root, childRTindex); + Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = NIL; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, + (Node *) rinfo->clause, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + /* track minimum security level among child quals */ + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might have + * securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals, see expand_inherited_rtentry().) Pull any such + * securityQuals up into the baserestrictinfo for the child. This is + * similar to process_security_barrier_quals() for the parent rel, + * except that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* not likely that we'd see constants here, so no check */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, false, false, + security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* + * OK, we've got all the baserestrictinfo quals for this child. + */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) + { + /* This partition was pruned; skip it. */ + set_dummy_rel_pathlist(childrel); + continue; + } + + if (relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* This child is excluded; skip it. */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Add missing Vars to child's reltarget. + * + * add_inherit_target_child_root() would've added only those that are + * needed to be present in the top-level tlist (or ones that + * preprocess_targetlist thinks are needed to be in the tlist.) We + * may need other attributes such as those contained in WHERE clauses, + * which are already computed for the parent during + * deconstruct_jointree processing of the original query (child's + * query never goes through deconstruct_jointree.) + */ + translated_exprs = (List *) + adjust_appendrel_attrs(root, + (Node *) rel->reltarget->exprs, + 1, &appinfo); + child_target_exprs = childrel->reltarget->exprs; + foreach(lc, translated_exprs) + { + Expr *expr = lfirst(lc); + + if (!list_member(child_target_exprs, expr)) + child_target_exprs = lappend(child_target_exprs, expr); + } + + subroot = root->inh_target_child_roots[childRTindex]; + + /* + * Also, We have to find any ECs containing parent's expressions and + * *replace* them with their copies containing child expressions. + */ + if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) + { + /* + * First make copies for ECs for the child's root, because some + * might be modified to replace parent EC expressions by child + * expressions in add_child_rel_equivalences. + */ + subroot->eq_classes = NIL; + foreach(lc, root->eq_classes) + { + EquivalenceClass *ec = lfirst(lc); + EquivalenceClass *new_ec = makeNode(EquivalenceClass); + + /* + * First memcpy which creates shallow copies of all + * the members and then make copies of members that could + * change. + * + * XXX comment in _copyPathKey says it's OK to recycle EC + * pointers, but as long as we do the whole planning for a + * given child using a given root, copying ECs like this + * shouldn't be a problem. Maybe, the following code could + * be in _copyEquivalenceClass()? + */ + memcpy(new_ec, ec, sizeof(EquivalenceClass)); + new_ec->ec_opfamilies = list_copy(ec->ec_opfamilies); + new_ec->ec_members = list_copy(ec->ec_members); + new_ec->ec_sources = list_copy(ec->ec_sources); + new_ec->ec_derives = list_copy(ec->ec_derives); + new_ec->ec_relids = bms_copy(ec->ec_relids); + + subroot->eq_classes = lappend(subroot->eq_classes, new_ec); + } + + add_child_rel_equivalences(subroot, appinfo, rel, childrel, true); + } + childrel->has_eclass_joins = rel->has_eclass_joins; + + /* Translate join quals. */ + childrel->joininfo = (List *) + adjust_appendrel_attrs(subroot, + (Node *) rel->joininfo, + 1, &appinfo); + + /* + * If parallelism is allowable for this query in general, see whether + * it's allowable for this childrel in particular. For consistency, + * do this before calling set_rel_size() for the child. + */ + if (root->glob->parallelModeOK) + set_rel_consider_parallel(subroot, childrel, childRTE); + + /* + * Compute the child's size using possibly modified subroot. + */ + set_rel_size(subroot, childrel, childRTindex, childRTE); + + /* If the child itself is partitioned it may turn into a dummy rel. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* We have at least one live child. */ + has_live_children = true; + + Assert(childrel->rows > 0); + } + + if (has_live_children) + { + /* + * Set a non-zero value here to cope with the caller's requirement + * that non-dummy relations are actually not empty. We don't try to + * be accurate here, because we're not going to create a path that + * combines the children outputs. + */ + rel->rows = 1; + } + else + { + /* + * All children were excluded by constraints, so mark the relation + * ass dummy. We must do this in this phase so that the rel's + * dummy-ness is visible when we generate paths for other rels. + */ + set_dummy_rel_pathlist(rel); + } +} + +/* + * set_inherit_target_rel_pathlists + * Build access paths for the child target relations + * + * Similar to set_append_rel_pathlist, except that we build paths of the + * children, but don't build an Append path. + */ +static void +set_inherit_target_rel_pathlists(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + ListCell *l; + + /* Nothing to do if all the children were excluded. */ + if (IS_DUMMY_REL(rel)) + return; + + /* Generate access paths for each of the children of passed-in rel */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + PlannerInfo *subroot; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + /* Re-locate the child RTE and RelOptInfo */ + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + childrel = root->simple_rel_array[childRTindex]; + subroot = root->inh_target_child_roots[childRTindex]; + /* Transfer the value from main root to subroot. */ + subroot->total_table_pages = root->total_table_pages; + + /* + * If set_append_rel_size() decided the parent appendrel was + * parallel-unsafe at some point after visiting this child rel, we + * need to propagate the unsafety marking down to the child, so that + * we don't generate useless partial paths for it. + */ + if (!rel->consider_parallel) + childrel->consider_parallel = false; + + /* + * Compute the child's access paths. + */ + set_rel_pathlist(subroot, childrel, childRTindex, childRTE); + } +} + +/* * set_append_rel_size * Set size estimates for a simple "append relation" * @@ -1184,7 +1591,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, * paths that produce those sort orderings). */ if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) - add_child_rel_equivalences(root, appinfo, rel, childrel); + add_child_rel_equivalences(root, appinfo, rel, childrel, false); childrel->has_eclass_joins = rel->has_eclass_joins; /* @@ -2625,6 +3032,140 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows) } /* + * inheritance_make_rel_from_joinlist + * Perform join planning for all non-dummy leaf inheritance children + * in their role as an UPDATE/DELETE query's target relation + * + * If a child relation is a partitioned table, its children are processed in + * turn by recursively calling this function. + */ +static void +inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; +#ifdef USE_ASSERT_CHECKING + Relids all_baserels; +#endif + + /* For UPDATE/DELETE queries, the top parent can only ever be a table. */ + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + Assert(planner_rt_fetch(resultRelation, root)->rtekind == RTE_RELATION); + + /* Nothing to do. */ + if (IS_DUMMY_REL(find_base_rel(root, resultRelation))) + return; + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + PlannerInfo *subroot; + RelOptInfo *childrel; + RelOptInfo *childjoinrel; + List *translated_joinlist; + + if (appinfo->parent_relid != resultRelation) + continue; + + childrel = find_base_rel(root, appinfo->child_relid); + + /* Ignore excluded/pruned children. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* Add this child. */ + root->inh_target_child_rels = lappend_int(root->inh_target_child_rels, + appinfo->child_relid); + + /* Perform join planning with child subroot. */ + subroot = root->inh_target_child_roots[appinfo->child_relid]; + Assert(subroot->parse->resultRelation > 0); + + /* + * Modify joinlist such that relations joined to the top parent rel + * appear to be joined to the child rel instead. Do the same for + * any SpecialJoinInfo structs. + */ + translated_joinlist = (List *) + adjust_appendrel_attrs(subroot, + (Node *) joinlist, + 1, &appinfo); + subroot->join_info_list = (List *) + adjust_appendrel_attrs(subroot, + (Node *) root->join_info_list, + 1, &appinfo); + + /* + * Sub-partitioned tables have to be processed recursively using the + * translated subroot as the parent, because AppendRelInfos link + * sub-partitions to their immediate parents, not the root partitioned + * table. + */ + if (childrel->part_scheme != NULL) + { + inheritance_make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * Add this child relation as a placeholder in the parent root's + * inh_target_child_joinrels so that inheritance_planner see same + * number of entries as inh_target_child_rels. + */ + root->inh_target_child_joinrels = + lappend(root->inh_target_child_joinrels, childrel); + + /* Also propagate this child's own children into parent's list. */ + if (subroot->inh_target_child_rels != NIL) + { + root->inh_target_child_rels = + list_concat(root->inh_target_child_rels, + subroot->inh_target_child_rels); + root->inh_target_child_joinrels = + list_concat(root->inh_target_child_joinrels, + subroot->inh_target_child_joinrels); + } + continue; + } + + /* + * Since we added the child rel directly into the join tree, we must + * modify it to be a "base" rel instead of an "other" rel, which the + * join planning code expects the relations being joined to be. + */ + childrel->reloptkind = RELOPT_BASEREL; + + Assert(subroot->join_rel_list == NIL); + Assert(subroot->join_rel_hash == NULL); + + /* Perform join planning and save the resulting RelOptInfo. */ + childjoinrel = make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * Remember this child target rel. inheritance_planner will perform + * the remaining steps of planning for each child relation separately. + * Specifically, it will call grouping_planner on every + * RelOptInfo contained in the inh_target_child_rels list, each of + * which represents the source of tuples to be modified for a given + * target child rel. + */ + root->inh_target_child_joinrels = + lappend(root->inh_target_child_joinrels, childjoinrel); +#ifdef USE_ASSERT_CHECKING + /* + * The following implements essentially the same Assert as in + * make_one_rel, our caller. + */ + all_baserels = bms_copy(root->all_baserels); + all_baserels = bms_del_member(all_baserels, + root->parse->resultRelation); + all_baserels = bms_add_member(all_baserels, + subroot->parse->resultRelation); + Assert(bms_equal(childjoinrel->relids, all_baserels)); +#endif + } +} + +/* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. * diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 6e134ae1d2..10136ef940 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -2104,12 +2104,16 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root, * * parent_rel and child_rel could be derived from appinfo, but since the * caller has already computed them, we might as well just pass them in. + * + * If 'replace' is true then the child EC members *replace* the corresponding + * parent members. */ void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, - RelOptInfo *child_rel) + RelOptInfo *child_rel, + bool replace) { ListCell *lc1; @@ -2117,14 +2121,23 @@ add_child_rel_equivalences(PlannerInfo *root, { EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc1); ListCell *lc2; + ListCell *prev; + ListCell *next; /* * If this EC contains a volatile expression, then generating child * EMs would be downright dangerous, so skip it. We rely on a - * volatile EC having only one EM. + * volatile EC having only one EM. If the caller asked to *replace* + * the original parent expression with the child one, then it's + * okay to proceed, because we wouldn't be adding another expression + * as being equivalent in that case. */ if (cur_ec->ec_has_volatile) - continue; + { + Assert(list_length(cur_ec->ec_members) == 1); + if (!replace) + continue; + } /* * No point in searching if parent rel not mentioned in eclass; but we @@ -2134,12 +2147,18 @@ add_child_rel_equivalences(PlannerInfo *root, !bms_is_subset(parent_rel->relids, cur_ec->ec_relids)) continue; - foreach(lc2, cur_ec->ec_members) + prev = NULL; + for (lc2 = list_head(cur_ec->ec_members); lc2 != NULL; lc2 = next) { EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2); + next = lnext(lc2); + if (cur_em->em_is_const) + { + prev = lc2; continue; /* ignore consts here */ + } /* Does it reference parent_rel? */ if (bms_overlap(cur_em->em_relids, parent_rel->relids)) @@ -2177,10 +2196,38 @@ add_child_rel_equivalences(PlannerInfo *root, child_rel->relids); } + /* + * Caller may have asked us to replace the parent expression + * with the child expression, so delete the parent expression + * first. + */ + if (replace) + cur_ec->ec_members = list_delete_cell(cur_ec->ec_members, + lc2, prev); + + /* + * If we're replacing, then the new member isn't really a + * a child, so em_is_child should be set to false. + */ (void) add_eq_member(cur_ec, child_expr, new_relids, new_nullable_relids, - true, cur_em->em_datatype); + !replace, cur_em->em_datatype); } + else + prev = lc2; + } + + if (replace) + { + /* + * Now fix up EC's relids set. It's OK to modify EC like this, + * because caller must have made a copy of the original EC. + * For example, see adjust_inherited_target_child_root. + */ + cur_ec->ec_relids = bms_difference(cur_ec->ec_relids, + parent_rel->relids); + cur_ec->ec_relids = bms_add_members(cur_ec->ec_relids, + child_rel->relids); } } } diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 8bfe9c3ff7..663130461a 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1463,7 +1463,7 @@ update_child_rel_info(PlannerInfo *root, /* Make child entries in the EquivalenceClass as well */ if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) - add_child_rel_equivalences(root, appinfo, rel, childrel); + add_child_rel_equivalences(root, appinfo, rel, childrel, false); childrel->has_eclass_joins = rel->has_eclass_joins; } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 97d0c28132..1e1c7efef9 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -2019,12 +2019,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) /* * During setrefs.c, we'll need the grouping_map to fix up the cols lists * in GroupingFunc nodes. Save it for setrefs.c to use. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be grouping in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->grouping_map == NULL); root->grouping_map = grouping_map; @@ -2181,12 +2176,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) * with InitPlan output params. (We can't just do that locally in the * MinMaxAgg node, because path nodes above here may have Agg references * as well.) Save the mmaggregates list to tell setrefs.c to do that. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be aggregates in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->minmax_aggs == NIL); root->minmax_aggs = best_path->mmaggregates; diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index fc97a1bb50..78557aeecb 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -28,7 +28,7 @@ #include "optimizer/paths.h" #include "optimizer/placeholder.h" #include "optimizer/planmain.h" - +#include "optimizer/prep.h" /* * query_planner @@ -59,6 +59,7 @@ query_planner(PlannerInfo *root, List *tlist, Query *parse = root->parse; List *joinlist; RelOptInfo *final_rel; + Index rti; /* * If the query has an empty join tree, then it's something easy like @@ -232,13 +233,52 @@ query_planner(PlannerInfo *root, List *tlist, extract_restriction_or_clauses(root); /* + * Construct the all_baserels Relids set. + */ + root->all_baserels = NULL; + for (rti = 1; rti < root->simple_rel_array_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + Assert(brel->relid == rti); /* sanity check on array */ + + /* ignore RTEs that are "other rels" */ + if (brel->reloptkind != RELOPT_BASEREL) + continue; + + root->all_baserels = bms_add_member(root->all_baserels, brel->relid); + } + + /* + * Add child subroots needed to use during planning for individual child + * targets + */ + if (root->inherited_update) + { + root->inh_target_child_roots = (PlannerInfo **) + palloc0(root->simple_rel_array_size * + sizeof(PlannerInfo *)); + add_inherited_target_child_roots(root); + } + + /* * Ready to do the primary planning. */ final_rel = make_one_rel(root, joinlist); - /* Check that we got at least one usable path */ - if (!final_rel || !final_rel->cheapest_total_path || - final_rel->cheapest_total_path->param_info != NULL) + /* + * Check that we got at least one usable path. In the case of an + * inherited update/delete operation, no path has been created for + * the query's actual target relation yet. + */ + if (!root->inherited_update && + (!final_rel || + !final_rel->cheapest_total_path || + final_rel->cheapest_total_path->param_info != NULL)) elog(ERROR, "failed to construct the join relation"); return final_rel; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 5ba612922f..703babfdf0 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -39,6 +39,7 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "nodes/relation.h" #include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" @@ -131,7 +132,7 @@ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static void inheritance_planner(PlannerInfo *root); static void grouping_planner(PlannerInfo *root, bool inheritance_update, - double tuple_fraction); + RelOptInfo *planned_rel, double tuple_fraction); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, int *tleref_to_colnum_map); @@ -635,7 +636,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->grouping_map = NULL; root->minmax_aggs = NIL; root->qual_security_level = 0; - root->inhTargetKind = INHKIND_NONE; root->hasRecursion = hasRecursion; if (hasRecursion) root->wt_param_id = assign_special_exec_param(root); @@ -725,6 +725,13 @@ subquery_planner(PlannerGlobal *glob, Query *parse, expand_inherited_tables(root); /* + * Now that we have figured out "actual" inheritance situation of the + * relations, set whether the query is an inherited UPDATE/DELETE. + */ + root->inherited_update = (parse->resultRelation && + rt_fetch(parse->resultRelation, parse->rtable)->inh); + + /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -975,7 +982,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh) inheritance_planner(root); else - grouping_planner(root, false, tuple_fraction); + grouping_planner(root, false, NULL, tuple_fraction); /* * Capture the set of outer-level param IDs we have access to, for use in @@ -1151,13 +1158,41 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr) * inheritance set. * * We have to handle this case differently from cases where a source relation - * is an inheritance set. Source inheritance is expanded at the bottom of the - * plan tree (see allpaths.c), but target inheritance has to be expanded at - * the top. The reason is that for UPDATE, each target relation needs a - * different targetlist matching its own column set. Fortunately, + * is an inheritance set, where outputs of all the children are combined and + * combined output consumed using the source relation's (parent's) column set. + * For UPDATE, each target relation, where the query's output will go, needs + * a different targetlist matching its own column set. So, we will need to + * translate the query such that it produces the desired output required by + * each of the child target relations and plan each separately. Fortunately, * the UPDATE/DELETE target can never be the nullable side of an outer join, * so it's OK to generate the plan this way. * + * Planning steps that need to be performed separately for each child and + * any preliminary processing needed for that are handled by the following + * sub-routines of query_planner: + * + * add_inherit_target_roots - this creates copies of PlannerInfo for each + * child after query_planner has finished processing the join tree and + * creating EquivalenceClasses, so that working state of planning need + * not be created afresh for each child, especially the various arrays + * + * set_inherit_target_rel_sizes - this sets size estimates for child + * relations, replace the parent EC members by corresponding child ones + * in their respective subroots + * + * set_inherit_target_rel_pathlists - this creates Paths for scanning + * individual child relations + * + * inherit_make_rel_from_joinlist - this translates the jointree, replacing + * the target relation in the original jointree (the root parent) by + * individual child target relations and performs join planning on the + * resulting join tree, saving the RelOptInfos of resulting join relations + * into the top-level PlannerInfo + * + * Finally, here we apply grouping_planner to each child join relation + * produced by set_inherit_target_rel_pathlists to so that its path produces + * the desired targetlist. + * * Returns nothing; the useful output is in the Paths we attach to * the (UPPERREL_FINAL, NULL) upperrel stored in *root. * @@ -1169,14 +1204,8 @@ inheritance_planner(PlannerInfo *root) { Query *parse = root->parse; int top_parentRTindex = parse->resultRelation; - Bitmapset *subqueryRTindexes; - Bitmapset *modifiableARIindexes; int nominalRelation = -1; Index rootRelation = 0; - List *final_rtable = NIL; - int save_rel_array_size = 0; - RelOptInfo **save_rel_array = NULL; - AppendRelInfo **save_append_rel_array = NULL; List *subpaths = NIL; List *subroots = NIL; List *resultRelations = NIL; @@ -1184,70 +1213,47 @@ inheritance_planner(PlannerInfo *root) List *returningLists = NIL; List *rowMarks; RelOptInfo *final_rel; - ListCell *lc; - Index rti; + ListCell *lc1, + *lc2; RangeTblEntry *parent_rte; - PlannerInfo *parent_root; - Query *parent_parse; - Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); - PlannerInfo **parent_roots = NULL; + List *tlist; + standard_qp_extra qp_extra; + RelOptInfo *planned_rel; + /* Inheritance is never used for insert. */ Assert(parse->commandType != CMD_INSERT); /* - * We generate a modified instance of the original Query for each target - * relation, plan that, and put all the plans into a list that will be - * controlled by a single ModifyTable node. All the instances share the - * same rangetable, but each instance must have its own set of subquery - * RTEs within the finished rangetable because (1) they are likely to get - * scribbled on during planning, and (2) it's not inconceivable that - * subqueries could get planned differently in different cases. We need - * not create duplicate copies of other RTE kinds, in particular not the - * target relations, because they don't have either of those issues. Not - * having to duplicate the target relations is important because doing so - * (1) would result in a rangetable of length O(N^2) for N targets, with - * at least O(N^3) work expended here; and (2) would greatly complicate - * management of the rowMarks list. + * Let query_planner generate the access paths for the query for each + * target child relation. * - * To begin with, generate a bitmapset of the relids of the subquery RTEs. + * First, save the unexpanded version of the query's targetlist. + * create_inherit_target_child_root will use it as base when expanding + * it for a given child relation as the query's target relation instead + * of the parent. */ - subqueryRTindexes = NULL; - rti = 1; - foreach(lc, parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - if (rte->rtekind == RTE_SUBQUERY) - subqueryRTindexes = bms_add_member(subqueryRTindexes, rti); - rti++; - } + root->unexpanded_tlist = list_copy(root->parse->targetList); + tlist = preprocess_targetlist(root); + root->processed_tlist = tlist; + qp_extra.tlist = tlist; + qp_extra.activeWindows = NIL; + qp_extra.groupClause = NIL; + planned_rel = query_planner(root, tlist, standard_qp_callback, &qp_extra); /* - * Next, we want to identify which AppendRelInfo items contain references - * to any of the aforesaid subquery RTEs. These items will need to be - * copied and modified to adjust their subquery references; whereas the - * other ones need not be touched. It's worth being tense over this - * because we can usually avoid processing most of the AppendRelInfo - * items, thereby saving O(N^2) space and time when the target is a large - * inheritance tree. We can identify AppendRelInfo items by their - * child_relid, since that should be unique within the list. + * If it turned out during query planning that all the children are dummy + * (pruned or excluded by constraints), no need to do the steps below. + * Let grouping_planner finish up the final path. */ - modifiableARIindexes = NULL; - if (subqueryRTindexes != NULL) + if (IS_DUMMY_REL(planned_rel)) { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - - if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) || - bms_is_member(appinfo->child_relid, subqueryRTindexes) || - bms_overlap(pull_varnos((Node *) appinfo->translated_vars), - subqueryRTindexes)) - modifiableARIindexes = bms_add_member(modifiableARIindexes, - appinfo->child_relid); - } + grouping_planner(root, false, planned_rel, 0.0); + return; } + Assert(planned_rel->relid == top_parentRTindex); + Assert(planned_rel->reloptkind == RELOPT_BASEREL); + /* * If the parent RTE is a partitioned table, we should use that as the * nominal target relation, because the RTEs added for partitioned tables @@ -1255,7 +1261,7 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); + parent_rte = planner_rt_fetch(top_parentRTindex, root); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1263,95 +1269,39 @@ inheritance_planner(PlannerInfo *root) } /* - * The PlannerInfo for each child is obtained by translating the relevant - * members of the PlannerInfo for its immediate parent, which we find - * using the parent_relid in its AppendRelInfo. We save the PlannerInfo - * for each parent in an array indexed by relid for fast retrieval. Since - * the maximum number of parents is limited by the number of RTEs in the - * query, we use that number to allocate the array. An extra entry is - * needed since relids start from 1. + * Get on with finalizing the path for each child target relation by + * calling grouping_planner on its joinrel. */ - parent_roots = (PlannerInfo **) palloc0((list_length(parse->rtable) + 1) * - sizeof(PlannerInfo *)); - parent_roots[top_parentRTindex] = root; - - /* - * And now we can get on with generating a plan for each child table. - */ - foreach(lc, root->append_rel_list) + forboth(lc1, root->inh_target_child_rels, + lc2, root->inh_target_child_joinrels) { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - PlannerInfo *subroot; + Index childRTindex = lfirst_int(lc1); + RelOptInfo *childjoinrel = lfirst(lc2); + PlannerInfo *subroot = root->inh_target_child_roots[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; RangeTblEntry *child_rte; RelOptInfo *sub_final_rel; Path *subpath; - /* append_rel_list contains all append rels; ignore others */ - if (!bms_is_member(appinfo->parent_relid, parent_relids)) - continue; - - /* - * expand_inherited_rtentry() always processes a parent before any of - * that parent's children, so the parent_root for this relation should - * already be available. - */ - parent_root = parent_roots[appinfo->parent_relid]; - Assert(parent_root != NULL); - parent_parse = parent_root->parse; - - /* - * We need a working copy of the PlannerInfo so that we can control - * propagation of information back to the main copy. - */ - subroot = makeNode(PlannerInfo); - memcpy(subroot, parent_root, sizeof(PlannerInfo)); - - /* - * Generate modified query with this rel as target. We first apply - * adjust_appendrel_attrs, which copies the Query and changes - * references to the parent RTE to refer to the current child RTE, - * then fool around with subquery RTEs. - */ - subroot->parse = (Query *) - adjust_appendrel_attrs(parent_root, - (Node *) parent_parse, - 1, &appinfo); + Assert(subroot != NULL); + Assert(subroot->parse->resultRelation == childRTindex); /* * If there are securityQuals attached to the parent, move them to the * child rel (they've already been transformed properly for that). */ - parent_rte = rt_fetch(appinfo->parent_relid, subroot->parse->rtable); - child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable); + parent_rte = planner_rt_fetch(appinfo->parent_relid, subroot); + child_rte = planner_rt_fetch(appinfo->child_relid, subroot); child_rte->securityQuals = parent_rte->securityQuals; parent_rte->securityQuals = NIL; /* - * Mark whether we're planning a query to a partitioned table or an - * inheritance parent. + * Ignore a partitioned child. Instead, the paths of its children will + * be added to subpaths. */ - subroot->inhTargetKind = - (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED; - - /* - * If this child is further partitioned, remember it as a parent. - * Since a partitioned table does not have any data, we don't need to - * create a plan for it, and we can stop processing it here. We do, - * however, need to remember its modified PlannerInfo for use when - * processing its children, since we'll update their varnos based on - * the delta from immediate parent to child, not from top to child. - * - * Note: a very non-obvious point is that we have not yet added - * duplicate subquery RTEs to the subroot's rtable. We mustn't, - * because then its children would have two sets of duplicates, - * confusing matters. - */ - if (child_rte->inh) + if (childjoinrel->part_scheme) { - Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE); - parent_relids = bms_add_member(parent_relids, appinfo->child_relid); - parent_roots[appinfo->child_relid] = subroot; - + Assert(child_rte->inh); continue; } @@ -1379,111 +1329,8 @@ inheritance_planner(PlannerInfo *root) if (nominalRelation < 0) nominalRelation = appinfo->child_relid; - /* - * The rowMarks list might contain references to subquery RTEs, so - * make a copy that we can apply ChangeVarNodes to. (Fortunately, the - * executor doesn't need to see the modified copies --- we can just - * pass it the original rowMarks list.) - */ - subroot->rowMarks = copyObject(parent_root->rowMarks); - - /* - * The append_rel_list likewise might contain references to subquery - * RTEs (if any subqueries were flattenable UNION ALLs). So prepare - * to apply ChangeVarNodes to that, too. As explained above, we only - * want to copy items that actually contain such references; the rest - * can just get linked into the subroot's append_rel_list. - * - * If we know there are no such references, we can just use the outer - * append_rel_list unmodified. - */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - subroot->append_rel_list = NIL; - foreach(lc2, parent_root->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, modifiableARIindexes)) - appinfo2 = copyObject(appinfo2); - - subroot->append_rel_list = lappend(subroot->append_rel_list, - appinfo2); - } - } - - /* - * Add placeholders to the child Query's rangetable list to fill the - * RT indexes already reserved for subqueries in previous children. - * These won't be referenced, so there's no need to make them very - * valid-looking. - */ - while (list_length(subroot->parse->rtable) < list_length(final_rtable)) - subroot->parse->rtable = lappend(subroot->parse->rtable, - makeNode(RangeTblEntry)); - - /* - * If this isn't the first child Query, generate duplicates of all - * subquery RTEs, and adjust Var numbering to reference the - * duplicates. To simplify the loop logic, we scan the original rtable - * not the copy just made by adjust_appendrel_attrs; that should be OK - * since subquery RTEs couldn't contain any references to the target - * rel. - */ - if (final_rtable != NIL && subqueryRTindexes != NULL) - { - ListCell *lr; - - rti = 1; - foreach(lr, parent_parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lr); - - if (bms_is_member(rti, subqueryRTindexes)) - { - Index newrti; - - /* - * The RTE can't contain any references to its own RT - * index, except in its securityQuals, so we can save a - * few cycles by applying ChangeVarNodes to the rest of - * the rangetable before we append the RTE to it. - */ - newrti = list_length(subroot->parse->rtable) + 1; - ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); - ChangeVarNodes((Node *) subroot->rowMarks, rti, newrti, 0); - /* Skip processing unchanging parts of append_rel_list */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - foreach(lc2, subroot->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, - modifiableARIindexes)) - ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); - } - } - rte = copyObject(rte); - ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); - subroot->parse->rtable = lappend(subroot->parse->rtable, - rte); - } - rti++; - } - } - - /* There shouldn't be any OJ info to translate, as yet */ - Assert(subroot->join_info_list == NIL); - /* and we haven't created PlaceHolderInfos, either */ - Assert(subroot->placeholder_list == NIL); - - /* Generate Path(s) for accessing this result relation */ - grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ ); + /* Apply the top-level targetlist to childjoinrel's path. */ + grouping_planner(subroot, true, childjoinrel, 0.0); /* * Select cheapest path in case there's more than one. We always run @@ -1495,45 +1342,10 @@ inheritance_planner(PlannerInfo *root) subpath = sub_final_rel->cheapest_total_path; /* - * If this child rel was excluded by constraint exclusion, exclude it - * from the result plan. + * child rel cannot be empty, or inheritance_make_rel_from_joinlist + * wouldn't have put it in the list to begin with. */ - if (IS_DUMMY_PATH(subpath)) - continue; - - /* - * If this is the first non-excluded child, its post-planning rtable - * becomes the initial contents of final_rtable; otherwise, append - * just its modified subquery RTEs to final_rtable. - */ - if (final_rtable == NIL) - final_rtable = subroot->parse->rtable; - else - final_rtable = list_concat(final_rtable, - list_copy_tail(subroot->parse->rtable, - list_length(final_rtable))); - - /* - * We need to collect all the RelOptInfos from all child plans into - * the main PlannerInfo, since setrefs.c will need them. We use the - * last child's simple_rel_array (previous ones are too short), so we - * have to propagate forward the RelOptInfos that were already built - * in previous children. - */ - Assert(subroot->simple_rel_array_size >= save_rel_array_size); - for (rti = 1; rti < save_rel_array_size; rti++) - { - RelOptInfo *brel = save_rel_array[rti]; - - if (brel) - subroot->simple_rel_array[rti] = brel; - } - save_rel_array_size = subroot->simple_rel_array_size; - save_rel_array = subroot->simple_rel_array; - save_append_rel_array = subroot->append_rel_array; - - /* Make sure any initplans from this rel get into the outer list */ - root->init_plans = subroot->init_plans; + Assert(!IS_DUMMY_PATH(subpath)); /* Build list of sub-paths */ subpaths = lappend(subpaths, subpath); @@ -1565,36 +1377,6 @@ inheritance_planner(PlannerInfo *root) */ /* - * If we managed to exclude every child rel, return a dummy plan; it - * doesn't even need a ModifyTable node. - */ - if (subpaths == NIL) - { - set_dummy_rel_pathlist(final_rel); - return; - } - - /* - * Put back the final adjusted rtable into the master copy of the Query. - * (We mustn't do this if we found no non-excluded children.) - */ - parse->rtable = final_rtable; - root->simple_rel_array_size = save_rel_array_size; - root->simple_rel_array = save_rel_array; - root->append_rel_array = save_append_rel_array; - - /* Must reconstruct master's simple_rte_array, too */ - root->simple_rte_array = (RangeTblEntry **) - palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *)); - rti = 1; - foreach(lc, final_rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - root->simple_rte_array[rti++] = rte; - } - - /* * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will * have dealt with fetching non-locked marked rows, else we need to have * ModifyTable do that. @@ -1634,6 +1416,12 @@ inheritance_planner(PlannerInfo *root) * (inheritance_planner will create a single ModifyTable node covering all the * target tables.) * + * If non-NULL, planned_rel is a RelOptInfo containing paths for the query's + * top-level joinrel, which the caller produced by itself. In that case, this + * function only needs to adjust the targetlist of its cheapest_total_path. + * The only caller that may pass such a RelOptInfo currently is + * inheritance_planner. + * * tuple_fraction is the fraction of tuples we expect will be retrieved. * tuple_fraction is interpreted as follows: * 0: expect all tuples to be retrieved (normal case) @@ -1652,6 +1440,7 @@ inheritance_planner(PlannerInfo *root) */ static void grouping_planner(PlannerInfo *root, bool inheritance_update, + RelOptInfo *planned_rel, double tuple_fraction) { Query *parse = root->parse; @@ -1664,7 +1453,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, List *final_targets; List *final_targets_contain_srfs; bool final_target_parallel_safe; - RelOptInfo *current_rel; + RelOptInfo *current_rel = planned_rel; RelOptInfo *final_rel; ListCell *lc; @@ -1704,6 +1493,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * special work for recursive unions is the responsibility of * plan_set_operations. */ + Assert(current_rel == NULL); current_rel = plan_set_operations(root); /* @@ -1793,17 +1583,26 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, parse->groupClause = preprocess_groupclause(root, NIL); } - /* Preprocess targetlist */ - tlist = preprocess_targetlist(root); - /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation so - * that we can transfer its decoration (resnames etc) to the topmost - * tlist of the finished Plan. + * Preprocess targetlist, if needed. If the caller has already done + * query planning, root->processed_tlist already contains the desired + * targetlist. */ - root->processed_tlist = tlist; + if (planned_rel == NULL) + { + tlist = preprocess_targetlist(root); + + /* + * We are now done hacking up the query's targetlist. Most of the + * remaining planning work will be done with the PathTarget + * representation of tlists, but save aside the full representation + * so that we can transfer its decoration (resnames etc) to the + * topmost tlist of the finished Plan. + */ + root->processed_tlist = tlist; + } + else + tlist = root->processed_tlist; /* * Collect statistics about aggregates for estimating costs, and mark @@ -1883,8 +1682,9 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * We also generate (in standard_qp_callback) pathkey representations * of the query's sort clause, distinct clause, etc. */ - current_rel = query_planner(root, tlist, - standard_qp_callback, &qp_extra); + if (current_rel == NULL) + current_rel = query_planner(root, tlist, + standard_qp_callback, &qp_extra); /* * Convert the query's result tlist into PathTarget format. diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 77dbf4eba3..87adfa02b1 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -917,7 +917,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->grouping_map = NULL; subroot->minmax_aggs = NIL; subroot->qual_security_level = 0; - subroot->inhTargetKind = INHKIND_NONE; subroot->hasRecursion = false; subroot->wt_param_id = -1; subroot->non_recursive_path = NULL; diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index ca6622ece9..34a3ebbbe4 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -394,8 +394,39 @@ adjust_appendrel_attrs_mutator(Node *node, context->appinfos); return (Node *) phv; } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index db474acbc5..0d2b42f72e 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -21,11 +21,14 @@ #include "miscadmin.h" #include "optimizer/appendinfo.h" #include "optimizer/inherit.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "utils/rel.h" +static PlannerInfo *create_inherited_target_child_root(PlannerInfo *root, + AppendRelInfo *appinfo); static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti); static void expand_partitioned_rtentry(PlannerInfo *root, @@ -492,3 +495,109 @@ translate_col_privs(const Bitmapset *parent_privs, return child_privs; } + +/* + * add_inherited_target_child_roots + * For each child of of the query's result relation, this translates the + * original query to match the child and creates a PlannerInfo containing + * the translated query + * + * Child PlannerInfo reuses most of the parent PlannerInfo's fields unchanged, + * except unexpanded_tlist, processed_tlist, and all_baserels, all of which + * are based on the child relation. + */ +void +add_inherited_target_child_roots(PlannerInfo *root) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; + + Assert(root->inh_target_child_roots != NULL); + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + RangeTblEntry *childRTE; + PlannerInfo *subroot; + + if (appinfo->parent_relid != resultRelation) + continue; + + /* + * Create a PlannerInfo for processing this child target relation + * with. + */ + subroot = create_inherited_target_child_root(root, appinfo); + root->inh_target_child_roots[appinfo->child_relid] = subroot; + + /* + * If the child is a partitioned table, recurse to do this for its + * partitions. + */ + childRTE = root->simple_rte_array[appinfo->child_relid]; + if (childRTE->inh) + add_inherited_target_child_roots(subroot); + } +} + +/* + * create_inherited_target_child_root + * Workhorse of add_inherited_target_child_roots + */ +static PlannerInfo * +create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) +{ + PlannerInfo *subroot; + List *tlist; + + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + + /* + * Translate the original query to replace Vars of the parent table + * by the corresponding Vars of the child table and to make child the main + * target relation of the query. + */ + subroot = makeNode(PlannerInfo); + memcpy(subroot, root, sizeof(PlannerInfo)); + + /* + * Restore the original, unexpanded targetlist, that is, the one before + * preprocess_targetlist was run on the original query. We'll run + * preprocess_targetlist after translating the query and the targetlist, + * so that it is expanded according to child's tuple descriptor. + */ + root->parse->targetList = root->unexpanded_tlist; + subroot->parse = (Query *) adjust_appendrel_attrs(root, + (Node *) root->parse, + 1, &appinfo); + + /* Save the just translated targetlist as unexpanded_tlist in the child's + * subroot, so that this child's own children can use it. Must use copy + * because subroot->parse->targetList will be modified soon. + */ + subroot->unexpanded_tlist = list_copy(subroot->parse->targetList); + + /* + * Apply planner's expansion of targetlist, such as adding various junk + * column, filling placeholder entries for dropped columns, etc., all of + * which occurs with the child's TupleDesc. + */ + tlist = preprocess_targetlist(subroot); + subroot->processed_tlist = tlist; + + /* Add any newly added Vars to the child RelOptInfo. */ + build_base_rel_tlists(subroot, tlist); + + /* + * Adjust all_baserels to replace the original target relation with the + * child target relation. Copy it before modifying though. + */ + subroot->all_baserels = bms_copy(root->all_baserels); + subroot->all_baserels = bms_del_member(subroot->all_baserels, + root->parse->resultRelation); + subroot->all_baserels = bms_add_member(subroot->all_baserels, + subroot->parse->resultRelation); + + return subroot; +} diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 48ffc5f254..6009de309e 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1264,36 +1264,6 @@ get_relation_constraints(PlannerInfo *root, } } - /* - * Append partition predicates, if any. - * - * For selects, partition pruning uses the parent table's partition bound - * descriptor, instead of constraint exclusion which is driven by the - * individual partition's partition constraint. - */ - if (enable_partition_pruning && root->parse->commandType != CMD_SELECT) - { - List *pcqual = RelationGetPartitionQual(relation); - - if (pcqual) - { - /* - * Run the partition quals through const-simplification similar to - * check constraints. We skip canonicalize_qual, though, because - * partition quals should be in canonical form already; also, - * since the qual is in implicit-AND format, we'd have to - * explicitly convert it to explicit-AND format and back again. - */ - pcqual = (List *) eval_const_expressions(root, (Node *) pcqual); - - /* Fix Vars to have the desired varno */ - if (varno != 1) - ChangeVarNodes((Node *) pcqual, 1, varno, 0); - - result = list_concat(result, pcqual); - } - } - heap_close(relation, NoLock); return result; @@ -1417,40 +1387,15 @@ relation_excluded_by_constraints(PlannerInfo *root, /* * Skip further tests, depending on constraint_exclusion. */ - switch (constraint_exclusion) - { - case CONSTRAINT_EXCLUSION_OFF: - - /* - * Don't prune if feature turned off -- except if the relation is - * a partition. While partprune.c-style partition pruning is not - * yet in use for all cases (update/delete is not handled), it - * would be a UI horror to use different user-visible controls - * depending on such a volatile implementation detail. Therefore, - * for partitioned tables we use enable_partition_pruning to - * control this behavior. - */ - if (root->inhTargetKind == INHKIND_PARTITIONED) - break; - return false; - - case CONSTRAINT_EXCLUSION_PARTITION: - - /* - * When constraint_exclusion is set to 'partition' we only handle - * OTHER_MEMBER_RELs, or BASERELs in cases where the result target - * is an inheritance parent or a partitioned table. - */ - if ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) && - !(rel->reloptkind == RELOPT_BASEREL && - root->inhTargetKind != INHKIND_NONE && - rel->relid == root->parse->resultRelation)) - return false; - break; - - case CONSTRAINT_EXCLUSION_ON: - break; /* always try to exclude */ - } + if (constraint_exclusion == CONSTRAINT_EXCLUSION_OFF) + return false; + /* + * When constraint_exclusion is set to 'partition' we only handle + * OTHER_MEMBER_RELs. + */ + else if (constraint_exclusion == CONSTRAINT_EXCLUSION_PARTITION && + rel->reloptkind != RELOPT_OTHER_MEMBER_REL) + return false; /* * Check for self-contradictory restriction clauses. We dare not make diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 3430061361..beb2e323a8 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -319,9 +319,6 @@ typedef struct PlannerInfo Index qual_security_level; /* minimum security_level for quals */ /* Note: qual_security_level is zero if there are no securityQuals */ - InheritanceKind inhTargetKind; /* indicates if the target relation is an - * inheritance child or partition or a - * partitioned table */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */ bool hasDeletedRTEs; /* true if any RTE was deleted from jointree */ @@ -343,6 +340,40 @@ typedef struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* + * The following fields are set during query planning portion of an + * inherited UPDATE/DEELETE operation. + */ + bool inherited_update; /* UPDATE/DELETE on inheritance parent? */ + + /* + * This stores the original version of the query's targetlist that's + * not modified by the planner. + */ + List *unexpanded_tlist; + + /* + * Array containing simple_rel_array_size elements, indexed by rangetable + * index (entry 0 is wasted like simple_rel_array). Only elements + * corresponding to individual inheritance child target relations are + * non-NULL. Content of each PlannerInfo is same as the parent + * PlannerInfo, except for the parse tree which is a translated copy of + * the parent's parse tree. + */ + struct PlannerInfo **inh_target_child_roots; + + /* List of RT indexes of child RT indexes. */ + List *inh_target_child_rels; + + /* + * RelOptInfos corresponding to each child target rel. For leaf children, + * it's the RelOptInfo representing the output of make_rel_from_joinlist() + * called with the parent rel in the original join tree replaced by a + * given leaf child. For non-leaf children, it's the baserel RelOptInfo + * itself, left as a placeholder. + */ + List *inh_target_child_joinrels; } PlannerInfo; diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h index b2687abd4e..1dbc2ca5bf 100644 --- a/src/include/optimizer/inherit.h +++ b/src/include/optimizer/inherit.h @@ -18,5 +18,6 @@ extern void expand_inherited_tables(PlannerInfo *root); +extern void add_inherited_target_child_roots(PlannerInfo *root); #endif /* INHERIT_H */ diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 666217c189..f524ab6beb 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -161,7 +161,8 @@ extern EquivalenceClass *match_eclasses_to_foreign_key_col(PlannerInfo *root, extern void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, - RelOptInfo *child_rel); + RelOptInfo *child_rel, + bool replace); extern List *generate_implied_equalities_for_column(PlannerInfo *root, RelOptInfo *rel, ec_matches_callback_type callback, diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index c55de5d476..c9f02c1fb0 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1780,7 +1780,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_1 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_1 -> Nested Loop Semi Join @@ -1788,7 +1788,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_2 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_2 (28 rows) -- 2.11.0
v16-0002-Lazy-creation-of-RTEs-for-inheritance-children.patch (text/plain, 97.0K) download From 71a7fae100bac86f0e392ed5cdb16a12761324fc Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 26 Oct 2018 16:45:59 +0900 Subject: [PATCH v16 2/4] Lazy creation of RTEs for inheritance children Currently, expand_inherited_tables called from subquery_planner opens all inheritance child tables and adds them to the query's range table and PlannerInfo in the form of AppendRelInfo's initially and later RelOptInfo's. For partitioning, that's pretty wasteful, because it's possible to determine only the partitions that will need to be scanned using partition pruning, which doesn't require opening the partitions themselves. However, the point at which expand_inherited_tables is called currently is too early to perform partition pruning. This commit rearranges things within the planner so that expand_inherited_tables can be called from query_planner after the latter has finished setting up the information needed to perform partition pruning. However that means the PlannerInfo arrays that store RangeTblEntry's, RelOptInfo's, and AppendRelInfo's need to be expanded if inheritance expansion leads to adding more relations to planning. There are also consequences around targetlist expansion. For example, preprocess_targetlist cannot add the junk columns entries needed for row mark handling until all of the inheritance children have been determined, because different children may need different types of junk columns to be added. So, the approach of late inheritance expansion required some interface tweaks for preprocess_targetlist(). For partitioning, although we don't create a RangeTblEntry and RelOptInfo for pruned partitions at make_one_rel time, partitionwise join code relies on the fact that even though partitions may have been pruned, they'd still own a RelOptInfo to handle the outer join case where the pruned partition appears on the nullable side of join. Partitionwise join code deals with that by allocating dummy RelOptInfos for pruned partitions that are based mostly on their parent's properties. There are some regression test diffs: 1. Caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child, as seen in the partition_aggregate.out test output. 2. Those in postgres_fdw.out are caused by the fact that junk columns required for row marking are added to reltarget->exprs later than user columns, because the row marking junk columns aren't added until the inheritance is expanded which as of this commit is later than it used to be as noted above. --- contrib/postgres_fdw/expected/postgres_fdw.out | 32 +- src/backend/nodes/outfuncs.c | 1 + src/backend/optimizer/path/allpaths.c | 343 +-------- src/backend/optimizer/path/joinrels.c | 61 ++ src/backend/optimizer/plan/initsplan.c | 58 -- src/backend/optimizer/plan/planmain.c | 10 + src/backend/optimizer/plan/planner.c | 74 +- src/backend/optimizer/plan/setrefs.c | 6 + src/backend/optimizer/prep/preptlist.c | 15 +- src/backend/optimizer/prep/prepunion.c | 3 + src/backend/optimizer/util/appendinfo.c | 45 +- src/backend/optimizer/util/inherit.c | 819 +++++++++++++++------- src/backend/optimizer/util/plancat.c | 47 +- src/backend/optimizer/util/relnode.c | 107 +-- src/backend/partitioning/partprune.c | 43 +- src/include/nodes/relation.h | 18 + src/include/optimizer/appendinfo.h | 7 +- src/include/optimizer/pathnode.h | 1 + src/include/optimizer/plancat.h | 4 +- src/include/optimizer/prep.h | 3 +- src/include/partitioning/partprune.h | 2 +- src/test/regress/expected/partition_aggregate.out | 4 +- 22 files changed, 932 insertions(+), 771 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index bb92d9d37a..33a33606ef 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7072,15 +7072,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7110,15 +7110,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7147,15 +7147,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid @@ -7165,15 +7165,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (39 rows) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 158da68925..67697b8884 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2272,6 +2272,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_BOOL_FIELD(consider_partitionwise_join); WRITE_BITMAPSET_FIELD(top_parent_relids); WRITE_NODE_FIELD(partitioned_child_rels); + WRITE_UINT_FIELD(inh_root_parent); } static void diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 61df22953a..ab4f376e92 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -45,7 +45,6 @@ #include "optimizer/var.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -383,6 +382,14 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, else if (rte->inh) { /* + * expand_inherited_tables may have proved that the relation is empty. + * For example, if it's a partitioned table with 0 partitions or all + * of its partitions are pruned. In that case nothing to do here. + */ + if (IS_DUMMY_REL(rel)) + return; + + /* * If it's a target relation, set the sizes of children instead. * Otherwise, we'll append the outputs of children, so process it as * an "append relation". @@ -951,38 +958,18 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, int parentRTindex = rti; bool has_live_children; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); Assert(IS_SIMPLE_REL(rel)); - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - foreach(l, root->append_rel_list) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; PlannerInfo *subroot; ListCell *lc; List *translated_exprs, @@ -994,141 +981,22 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); + Assert(childrel != NULL); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals. */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); + if (IS_DUMMY_REL(childrel)) continue; - } - - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } if (relation_excluded_by_constraints(root, childrel, childRTE)) { @@ -1181,15 +1049,9 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, EquivalenceClass *new_ec = makeNode(EquivalenceClass); /* - * First memcpy which creates shallow copies of all - * the members and then make copies of members that could - * change. - * - * XXX comment in _copyPathKey says it's OK to recycle EC - * pointers, but as long as we do the whole planning for a - * given child using a given root, copying ECs like this - * shouldn't be a problem. Maybe, the following code could - * be in _copyEquivalenceClass()? + * First memcpy the original EC, which creates shallow copies + * of all the members and then make copies of members that + * might change during add_child_rel_equivalences(). */ memcpy(new_ec, ec, sizeof(EquivalenceClass)); new_ec->ec_opfamilies = list_copy(ec->ec_opfamilies); @@ -1332,8 +1194,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -1341,32 +1201,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Assert(IS_SIMPLE_REL(rel)); /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, we will bubble up - * the indexes of partitioned relations that appear down in the tree, so - * that when we've created Paths for all the children, the root - * partitioned table's list will contain all such indexes. - */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - rel->partitioned_child_rels = list_make1_int(rti); - - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - - /* * If this is a partitioned baserel, set the consider_partitionwise_join * flag; currently, we only consider partitionwise joins with the baserel * if its targetlist doesn't contain a whole-row Var. @@ -1403,12 +1237,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; ListCell *parentvars; ListCell *childvars; - ListCell *lc; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1416,142 +1246,22 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); + Assert(childrel != NULL); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); /* - * We have to copy the parent's targetlist and quals to the child, - * with appropriate substitution of variables. However, only the - * baserestrictinfo quals are needed before we can check for - * constraint exclusion; so do that first and then check to see if we - * can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals. */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); + if (IS_DUMMY_REL(childrel)) continue; - } - - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } if (relation_excluded_by_constraints(root, childrel, childRTE)) { @@ -1564,7 +1274,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, } /* - * CE failed, so finish copying/modifying targetlist and join quals. + * Copy/Modify targetlist. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -3069,6 +2779,7 @@ inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) continue; childrel = find_base_rel(root, appinfo->child_relid); + Assert(childrel != NULL); /* Ignore excluded/pruned children. */ if (IS_DUMMY_REL(childrel)) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 663130461a..bc89de9a9d 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -20,6 +20,7 @@ #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" +#include "optimizer/tlist.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -51,6 +52,9 @@ static SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, Relids left_relids, Relids right_relids); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + int partidx); /* @@ -1383,6 +1387,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (IS_SIMPLE_REL(rel1) && child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts); + if (IS_SIMPLE_REL(rel1) && child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts); + /* * If a child table has consider_partitionwise_join=false, it means * that it's a dummy relation for which we skipped setting up tlist @@ -1674,3 +1683,55 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +static RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx) +{ + RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + AppendRelInfo *appinfo = make_append_rel_info(parent, parentrte, + parent->tupdesc, + parentrte->relid, + parent->reltype, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index a66374094f..c628f3d9e7 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -616,64 +616,6 @@ create_lateral_join_info(PlannerInfo *root) bms_add_member(brel2->lateral_referencers, rti); } } - - /* - * Lastly, propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - */ - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - RangeTblEntry *brte = root->simple_rte_array[rti]; - - /* - * Skip empty slots. Also skip non-simple relations i.e. dead - * relations. - */ - if (brel == NULL || !IS_SIMPLE_REL(brel)) - continue; - - /* - * In the case of table inheritance, the parent RTE is directly linked - * to every child table via an AppendRelInfo. In the case of table - * partitioning, the inheritance hierarchy is expanded one level at a - * time rather than flattened. Therefore, an other member rel that is - * a partitioned table may have children of its own, and must - * therefore be marked with the appropriate lateral info so that those - * children eventually get marked also. - */ - Assert(brte); - if (brel->reloptkind == RELOPT_OTHER_MEMBER_REL && - (brte->rtekind != RTE_RELATION || - brte->relkind != RELKIND_PARTITIONED_TABLE)) - continue; - - if (brte->inh) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); - RelOptInfo *childrel; - - if (appinfo->parent_relid != rti) - continue; - childrel = root->simple_rel_array[appinfo->child_relid]; - Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - Assert(childrel->direct_lateral_relids == NULL); - childrel->direct_lateral_relids = brel->direct_lateral_relids; - Assert(childrel->lateral_relids == NULL); - childrel->lateral_relids = brel->lateral_relids; - Assert(childrel->lateral_referencers == NULL); - childrel->lateral_referencers = brel->lateral_referencers; - } - } - } } diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 78557aeecb..62e11157da 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -254,6 +254,16 @@ query_planner(PlannerInfo *root, List *tlist, } /* + * Expand RT entries that represent inherited or partitioned tables. + * This will perform partition pruning on partitioned tables in the + * original range table and also recursively on any child partitioned + * tables that were added by the expansion of the original parent(s). + * As new entries are added to the range table, various arrays in the + * PlannerInfo will be expanded accordingly. + */ + expand_inherited_tables(root); + + /* * Add child subroots needed to use during planning for individual child * targets */ diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 703babfdf0..f80f35eed3 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -25,6 +25,7 @@ #include "access/sysattr.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -704,27 +705,18 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } /* - * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). - */ - preprocess_rowmarks(root); - - /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain base relations not joins, so it's OK (and marginally more - * efficient) to do it after checking for join RTEs. We must do it after - * pulling up subqueries, else we'd fail to handle inherited tables in - * subqueries. - */ - expand_inherited_tables(root); - - /* * Now that we have figured out "actual" inheritance situation of the * relations, set whether the query is an inherited UPDATE/DELETE. */ @@ -732,6 +724,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh); /* + * Preprocess RowMark information. We need to do this after subquery + * pullup (so that all non-inherited RTEs are present). + */ + preprocess_rowmarks(root); + + /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -1233,7 +1231,9 @@ inheritance_planner(PlannerInfo *root) * of the parent. */ root->unexpanded_tlist = list_copy(root->parse->targetList); - tlist = preprocess_targetlist(root); + + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root, false); root->processed_tlist = tlist; qp_extra.tlist = tlist; qp_extra.activeWindows = NIL; @@ -1243,9 +1243,11 @@ inheritance_planner(PlannerInfo *root) /* * If it turned out during query planning that all the children are dummy * (pruned or excluded by constraints), no need to do the steps below. - * Let grouping_planner finish up the final path. + * For regular inheritance case, it's possible that we only need to modify + * the parent table. Let grouping_planner finish up the final path. */ - if (IS_DUMMY_REL(planned_rel)) + parent_rte = planner_rt_fetch(top_parentRTindex, root); + if (!parent_rte->inh || IS_DUMMY_REL(planned_rel)) { grouping_planner(root, false, planned_rel, 0.0); return; @@ -1261,7 +1263,6 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = planner_rt_fetch(top_parentRTindex, root); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1590,14 +1591,19 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, */ if (planned_rel == NULL) { - tlist = preprocess_targetlist(root); + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root, false); /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation - * so that we can transfer its decoration (resnames etc) to the - * topmost tlist of the finished Plan. + * We are now mostly done hacking up the query's targetlist. More + * columns might be added during query_planner after inheritance + * expansion in make_one_rel, because some children may require + * different column than the parent for row mark handling; see + * add_rowmark_junk_columns(). Most of the remaining planning + * work will be done with the PathTarget representation of tlists, + * but save aside the full representation so that we can transfer + * its decoration (resnames etc) to the topmost tlist of the + * finished Plan. */ root->processed_tlist = tlist; } @@ -2373,7 +2379,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2398,7 +2404,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } @@ -6835,6 +6841,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -6936,6 +6946,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 5d363edab8..4dcdede6b6 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -325,6 +325,12 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) UPPERREL_FINAL, NULL))) add_rtes_to_flat_rtable(rel->subroot, true); } + /* + * A NULL rel also means an unplanned subquery rte, so apply + * flatten_unplanned_rtes. + */ + else + flatten_unplanned_rtes(glob, rte); } rti++; } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index f8bc2dd257..a55949c305 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -65,9 +65,13 @@ static List *expand_targetlist(List *tlist, int command_type, * * As a side effect, if there's an ON CONFLICT UPDATE clause, its targetlist * is also preprocessed (and updated in-place). + * + * inheritance_expanded specifies whether inheritance tables are expanded + * as of calling this function. Caller must specify it because that affects + * which row marking related junk columns get added to the targetlist. */ List * -preprocess_targetlist(PlannerInfo *root) +preprocess_targetlist(PlannerInfo *root, bool inheritance_expanded) { Query *parse = root->parse; int result_relation = parse->resultRelation; @@ -134,6 +138,15 @@ preprocess_targetlist(PlannerInfo *root) if (rc->rti != rc->prti) continue; + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which will change the parent row + * mark's allMarkTypes fields. + */ + if (rc->isParent && !inheritance_expanded) + continue; + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) { /* Need to fetch TID */ diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 55eeb5127c..d4d36248a1 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -31,13 +31,16 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" +#include "optimizer/var.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" #include "utils/lsyscache.h" diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index 34a3ebbbe4..d2c3c7b590 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -31,10 +31,10 @@ typedef struct AppendRelInfo **appinfos; } adjust_appendrel_attrs_context; -static void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, adjust_appendrel_attrs_context *context); static List *adjust_inherited_tlist(List *tlist, @@ -46,18 +46,20 @@ static List *adjust_inherited_tlist(List *tlist, * Build an AppendRelInfo for the parent-child pair */ AppendRelInfo * -make_append_rel_info(Relation parentrel, Relation childrel, - Index parentRTindex, Index childRTindex) +make_append_rel_info(RelOptInfo *parent, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childtype, + Index childRTindex) { AppendRelInfo *appinfo = makeNode(AppendRelInfo); - appinfo->parent_relid = parentRTindex; + appinfo->parent_relid = parent->relid; appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->rd_rel->reltype; - appinfo->child_reltype = childrel->rd_rel->reltype; - make_inh_translation_list(parentrel, childrel, childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = RelationGetRelid(parentrel); + appinfo->parent_reltype = parent->reltype; + appinfo->child_reltype = childtype; + make_inh_translation_list(parent->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; return appinfo; } @@ -70,14 +72,11 @@ make_append_rel_info(Relation parentrel, Relation childrel, * For paranoia's sake, we match type/collation as well as attribute name. */ static void -make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, - List **translated_vars) +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) { List *vars = NIL; - TupleDesc old_tupdesc = RelationGetDescr(oldrelation); - TupleDesc new_tupdesc = RelationGetDescr(newrelation); - Oid new_relid = RelationGetRelid(newrelation); int oldnatts = old_tupdesc->natts; int newnatts = new_tupdesc->natts; int old_attno; @@ -107,7 +106,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, * When we are generating the "translation list" for the parent table * of an inheritance set, no need to search for matches. */ - if (oldrelation == newrelation) + if (from_rel == to_rel) { vars = lappend(vars, makeVar(newvarno, (AttrNumber) (old_attno + 1), @@ -133,10 +132,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, { HeapTuple newtup; - newtup = SearchSysCacheAttName(new_relid, attname); + newtup = SearchSysCacheAttName(to_rel, attname); if (!newtup) elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; ReleaseSysCache(newtup); @@ -146,10 +145,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, /* Found it, check type and collation match */ if (atttypid != att->atttypid || atttypmod != att->atttypmod) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); if (attcollation != att->attcollation) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); vars = lappend(vars, makeVar(newvarno, (AttrNumber) (new_attno + 1), diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 0d2b42f72e..f1bd954373 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -18,33 +18,43 @@ #include "access/sysattr.h" #include "catalog/partition.h" #include "catalog/pg_inherits.h" +#include "catalog/pg_type.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" #include "optimizer/inherit.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" +#include "optimizer/var.h" +#include "partitioning/partprune.h" #include "utils/rel.h" static PlannerInfo *create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo); -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); +static void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti); +static void expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, + RangeTblEntry *rte, Index rti, RelOptInfo *rel); static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); - +static RelOptInfo *build_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); /* * expand_inherited_tables @@ -52,37 +62,145 @@ static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, * into an "append relation". At the conclusion of this process, * the "inh" flag is set in all and only those RTEs that are append * relation parents. + * + * Note that although we're calling the combined output of inheritance set + * relations an "append relation" here, the caller may not always want to + * combine the relations. For example, if the parent of the inheritance + * set is the query's target relation, each child relation is processed + * on its own as the query's target relation. */ void expand_inherited_tables(PlannerInfo *root) { - Index nrtes; + int orig_rtable_size; Index rti; - ListCell *rl; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; /* * expand_inherited_rtentry may add RTEs to parse->rtable. The function is * expected to recursively handle any RTEs that it creates with inh=true. * So just scan as far as the original end of the rtable list. */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) + for (rti = 1; rti < orig_rtable_size; rti++) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + if (rte->inh) + expand_inherited_rtentry(root, brel, rte, rti); } } /* * expand_inherited_rtentry - * Check whether a rangetable entry represents an inheritance set. - * If so, add entries for all the child tables to the query's - * rangetable, and build AppendRelInfo nodes for all the child tables - * and add them to root->append_rel_list. If not, clear the entry's - * "inh" flag to prevent later code from looking for AppendRelInfos. + * This initializes RelOptInfos for inheritance child relations if the + * passed-in relation has any + * + * 'rel' is the parent relation, whose range table entry ('rte') has been + * marked to require adding children. Parent could either be a subquery (if + * we flattened UNION ALL query) or a table that's known to have (or once had) + * inheritance children. The latter consists of both regular inheritance + * parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as a child to be scanned as part of the + * inheritance set. The original RTE is considered to represent the whole + * inheritance set. + */ +static void +expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_inheritance_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_nonpartitioned_inherited_rtentry(root, rte, rti, rel); + } +} + +/* + * expand_nonpartitioned_inherited_rtentry + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. * * Note that the original RTE is considered to represent the whole * inheritance set. The first of the generated RTEs is an RTE for the same @@ -93,55 +211,35 @@ expand_inherited_tables(PlannerInfo *root) * regular inheritance, a parent RTE must always have at least two associated * AppendRelInfos: one corresponding to the parent table as a simple member of * inheritance set and one or more corresponding to the actual children. - * Since a partitioned table is not scanned, it might have only one associated - * AppendRelInfo. */ static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, + RangeTblEntry *rte, + Index rti, + RelOptInfo *rel) { Oid parentOID; PlanRowMark *oldrc; - Relation oldrelation; - LOCKMODE lockmode; + LOCKMODE lockmode = rte->rellockmode; List *inhOIDs; ListCell *l; + int num_children; + int num_children_added = 0; - /* Does RT entry allow inheritance? */ - if (!rte->inh) - return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } - /* Fast path for common case of childless table */ + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); parentOID = rte->relid; - if (!has_subclass(parentOID)) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * The rewriter should already have obtained an appropriate lock on each - * relation named in the query. However, for each child relation we add - * to the query, we must obtain an appropriate lock, because this will be - * the first use of those relations in the parse/rewrite/plan pipeline. - * Child rels should use the same lockmode as their parent. - */ - lockmode = rte->rellockmode; /* Scan for all members of inheritance set, acquire needed locks */ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); /* * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite above has_subclass() check, if table - * once had a child but no longer does. + * case. This could happen despite has_subclass() check performed by + * subquery_planner, if table once had a child but no longer does. */ - if (list_length(inhOIDs) < 2) + num_children = list_length(inhOIDs); + if (num_children < 2) { /* Clear flag before returning */ rte->inh = false; @@ -149,216 +247,221 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } /* - * If parent relation is selected FOR UPDATE/SHARE, we need to mark its - * PlanRowMark as isParent = true, and generate a new PlanRowMark for each - * child. + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. */ oldrc = get_plan_rowmark(root->rowMarks, rti); - if (oldrc) - oldrc->isParent = true; + Assert(oldrc == NULL || oldrc->isParent); /* - * Must open the parent relation to examine its tupdesc. We need not lock - * it; we assume the rewriter already did. + * Must expand PlannerInfo arrays by num_children before we can add + * children. */ - oldrelation = heap_open(parentOID, NoLock); + Assert(num_children > 0); + expand_planner_arrays(root, num_children); - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) + foreach(l, inhOIDs) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); - - /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. - */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); - } - else - { - List *appinfos = NIL; + Oid childOID = lfirst_oid(l); + Relation newrelation; RangeTblEntry *childrte; Index childRTindex; + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. */ - foreach(l, inhOIDs) + if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = heap_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - heap_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - heap_close(newrelation, NoLock); + heap_close(newrelation, lockmode); + continue; } - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); + (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation, &childrte, + &childRTindex); + Assert(childrte != NULL); + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + Assert(childRTindex > 0); + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; } - heap_close(oldrelation, NoLock); + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } } /* * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) + Index parentRTindex, RelOptInfo *parentrel) { + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; - check_stack_depth(); - - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + if (bms_num_members(partindexes) > 0) + expand_planner_arrays(root, bms_num_members(partindexes)); /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. */ - if (partdesc->nparts == 0) - { - parentrte->inh = false; - return; - } + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); - for (i = 0; i < partdesc->nparts; i++) + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) { - Oid childOID = partdesc->oids[i]; - Relation childrel; + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + RangeTblEntry *childrte; + Index childRTindex; - /* Open rel; we already have required locks */ - childrel = heap_open(childOID, NoLock); + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation, + &childrte, &childRTindex); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + + /* If the child is partitioned itself, expand it too. */ + if (childrel->part_scheme) + { + Assert(childrte->inh); expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); + childrel); + } + } - /* Close child relation, but keep locks */ - heap_close(childrel, NoLock); + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); } } /* - * expand_single_inheritance_child - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) + * The return value is the RelOptInfo that's added. * * PlanRowMarks still carry the top-parent's RTI, and the top-parent's * allMarkTypes field still accumulates values from all descendents. * * "parentrte" and "parentRTindex" are immediate parent's RTE and * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. */ -static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); Oid childOID = RelationGetRelid(childrel); RangeTblEntry *childrte; Index childRTindex; AppendRelInfo *appinfo; + RelOptInfo *childrelopt; /* * Build an RTE for the child, and attach to query's rangetable list. We * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh = false. Also, set requiredPerms to zero since - * all required permissions checks are done on the original RTE. Likewise, - * set the child's securityQuals to empty, because we only want to apply - * the parent's RLS conditions regardless of what RLS properties + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties * individual children may have. (This is an intentional choice to make * inherited RLS work like regular permissions checks.) The parent * securityQuals will be propagated to children along with other base @@ -368,49 +471,42 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childrte_p = childrte; childrte->relid = childOID; childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) - childrte->inh = true; - else - childrte->inh = false; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); childrte->requiredPerms = 0; childrte->securityQuals = NIL; parse->rtable = lappend(parse->rtable, childrte); childRTindex = list_length(parse->rtable); *childRTindex_p = childRTindex; - /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. - */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) - { - appinfo = make_append_rel_info(parentrel, childrel, - parentRTindex, childRTindex); - *appinfos = lappend(*appinfos, appinfo); + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + RelationGetDescr(childrel), + RelationGetRelid(childrel), + RelationGetForm(childrel)->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); } /* @@ -442,6 +538,253 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need to create one, + * because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_inheritance_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; +} + +/* + * build_inheritance_child_rel + * Build a RelOptInfo for child relation of an inheritance set + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields based on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turn out to be self-contradictory, + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + List *childquals; + ListCell *lc; + bool have_const_false_cq; + Index cq_min_security; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = false; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, parent->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual = (Node *) rinfo->clause; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, childqual, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might + * have securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals.) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except + * that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* + * not likely that we'd see constants here, so no + * check + */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, + false, + false, + security_level, + NULL, NULL, + NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* Set child's version of baserestrictinfo. */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; } /* @@ -583,7 +926,7 @@ create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) * column, filling placeholder entries for dropped columns, etc., all of * which occurs with the child's TupleDesc. */ - tlist = preprocess_targetlist(subroot); + tlist = preprocess_targetlist(subroot, true); subroot->processed_tlist = tlist; /* Add any newly added Vars to the child RelOptInfo. */ diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 6009de309e..293f9ac619 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -105,20 +105,20 @@ static void set_baserel_partition_key_exprs(Relation relation, * important for it. */ void -get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) +get_relation_info(PlannerInfo *root, RangeTblEntry *rte, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; bool hasindex; List *indexinfos = NIL; + bool inhparent = rte->inh; /* * We need not lock the relation since it was already locked, either by * the rewriter or when expand_inherited_rtentry() added it to the query's * rangetable. */ - relation = heap_open(relationObjectId, NoLock); + relation = heap_open(rte->relid, NoLock); /* Temporary and unlogged relations are inaccessible during recovery. */ if (!RelationNeedsWAL(relation) && RecoveryInProgress()) @@ -444,11 +444,32 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, get_relation_foreign_keys(root, rel, relation, inhparent); /* - * Collect info about relation's partitioning scheme, if any. Only - * inheritance parents may be partitioned. + * Collect some additional information for inheritance parents. */ - if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - set_relation_partition_info(root, rel, relation); + if (inhparent) + { + /* + * We'll need the TupleDesc when initializing the child relation. + * A copy is being made because concurrent changes might drop + * the relcache entry. That's possible because ALTER TABLE + * child_table NO INHERIT parent_table only requires an + * AccessShareLock on parent_table. + */ + rel->tupdesc = CreateTupleDescCopy(RelationGetDescr(relation)); + rel->reltype = RelationGetForm(relation)->reltype; + + /* + * If partitioned, also save the information of partitioning scheme, + * and whether the query updates any of the partition key columns. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + set_relation_partition_info(root, rel, relation); + root->partColsUpdated |= has_partition_attrs(relation, + rte->updatedCols, + NULL); + } + } heap_close(relation, NoLock); @@ -458,7 +479,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * removing an index, or adding a hypothetical index to the indexlist. */ if (get_relation_info_hook) - (*get_relation_info_hook) (root, relationObjectId, inhparent, rel); + (*get_relation_info_hook) (root, rte->relid, rte->inh, rel); } /* @@ -1844,16 +1865,20 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; + + /* + * Since we must've taken a lock on the table, it's okay to simply copy + * the pointers to relcache data here. + */ + rel->part_oids = partdesc->oids; + rel->boundinfo = partdesc->boundinfo; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index fe83ec4519..d12d047002 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -132,6 +132,50 @@ setup_append_rel_array(PlannerInfo *root) } /* + * expand_planner_arrays + * Expand the PlannerInfo arrays by add_size members and initialize the + * the newly added bytes with zero + */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size; + + Assert(add_size > 0); + + new_size = root->simple_rel_array_size + add_size; + + /* Expand various arrays and 0-initialize added bytes. */ + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + + if (root->append_rel_array) + { + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + } + else + { + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + } + + root->simple_rel_array_size = new_size; +} + +/* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. */ @@ -195,6 +239,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->joininfo = NIL; rel->has_eclass_joins = false; rel->consider_partitionwise_join = false; /* might get changed later */ + rel->top_parent_relids = NULL; /* might be changed later */ rel->part_scheme = NULL; rel->nparts = 0; rel->boundinfo = NULL; @@ -203,6 +248,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->partexprs = NULL; rel->nullable_partexprs = NULL; rel->partitioned_child_rels = NIL; + rel->inh_root_parent = 0; /* might be changed later */ /* * Pass top parent's relids down the inheritance hierarchy. If the parent @@ -216,16 +262,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->top_parent_relids = parent->top_parent_relids; else rel->top_parent_relids = bms_copy(parent->relids); + + /* + * For inheritance child relations, we also set inh_root_parent. + * Note that 'parent' might itself be a child (a sub-partitioned + * partition), in which case we simply use its value of + * inh_root_parent. + */ + if (parent->rtekind == RTE_RELATION) + rel->inh_root_parent = parent->inh_root_parent > 0 ? + parent->inh_root_parent : + parent->relid; } - else - rel->top_parent_relids = NULL; /* Check type of rtable entry */ switch (rte->rtekind) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte, rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -266,52 +321,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); - /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. - */ - if (rte->inh) - { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); - - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; - } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); - } - return rel; } diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 901433c68c..9bb472fd98 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -46,6 +46,7 @@ #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" #include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/planner.h" #include "optimizer/predtest.h" @@ -437,17 +438,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -541,23 +548,20 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ @@ -565,6 +569,13 @@ prune_append_rel_partitions(RelOptInfo *rel) return NULL; /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. */ @@ -591,15 +602,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index beb2e323a8..01a664d246 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -15,6 +15,7 @@ #define RELATION_H #include "access/sdir.h" +#include "access/tupdesc.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/params.h" @@ -729,11 +730,28 @@ typedef struct RelOptInfo int nparts; /* number of partitions */ struct PartitionBoundInfoData *boundinfo; /* Partition bounds */ List *partition_qual; /* partition constraint */ + Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ + + /* + * For inheritance children, this is the RT index of inheritance table + * mentioned in the query from which this relation originated. + * top_parent_relids cannot be used for this, because if the inheritance + * root table is itself under UNION ALL, top_parent_relids contains the + * RT index of UNION ALL parent subquery. + */ + Index inh_root_parent; + + /* + * Set only if this is an inheritance parent relation. This information + * is needed when initializing the planning info for children. + */ + TupleDesc tupdesc; /* A "copy" of the table's tuple desriptor */ + Oid reltype; /* Table's reltype */ } RelOptInfo; /* diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h index 604e36d73c..8f7b06a608 100644 --- a/src/include/optimizer/appendinfo.h +++ b/src/include/optimizer/appendinfo.h @@ -17,9 +17,10 @@ #include "nodes/relation.h" #include "utils/relcache.h" -extern AppendRelInfo *make_append_rel_info(Relation parentrel, - Relation childrel, - Index parentRTindex, Index childRTindex); +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parent, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childtype, + Index childRTindex); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index bd905d3328..e7d0a20c54 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -261,6 +261,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index a1b23251a1..d8091cdfa4 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -25,8 +25,8 @@ typedef void (*get_relation_info_hook_type) (PlannerInfo *root, extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; -extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); +extern void get_relation_info(PlannerInfo *root, RangeTblEntry *rte, + RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 62d45dd142..a329e87fc2 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -38,7 +38,8 @@ extern Expr *canonicalize_qual(Expr *qual, bool is_check); /* * prototypes for preptlist.c */ -extern List *preprocess_targetlist(PlannerInfo *root); +extern List *preprocess_targetlist(PlannerInfo *root, + bool inheritance_expanded); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h index ce9bc8d9fd..5c5d27d040 100644 --- a/src/include/partitioning/partprune.h +++ b/src/include/partitioning/partprune.h @@ -76,7 +76,7 @@ extern PartitionPruneInfo *make_partition_pruneinfo(PlannerInfo *root, List *subpaths, List *partitioned_rels, List *prunequal); -extern Relids prune_append_rel_partitions(RelOptInfo *rel); +extern Bitmapset *prune_append_rel_partitions(RelOptInfo *rel); extern Bitmapset *get_matching_partitions(PartitionPruneContext *context, List *pruning_steps); diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 6bc106831e..1450cef057 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) -- 2.11.0
v16-0003-Teach-planner-to-only-process-unpruned-partition.patch (text/plain, 6.8K) download From 3a2886cc047786892eea1ece2ba27b034860f9d2 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 15 Oct 2018 10:59:24 +0900 Subject: [PATCH v16 3/4] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/inherit.c | 1 + src/backend/optimizer/util/relnode.c | 3 +++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/relation.h | 4 ++++ 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index bc89de9a9d..3d5b2df8d8 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1451,6 +1451,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index f80f35eed3..faf5f343fb 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6833,7 +6833,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -6841,9 +6843,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -6924,7 +6924,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -6936,7 +6935,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -6946,9 +6947,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index f1bd954373..d6f120fcc1 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -361,6 +361,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, /* Perform pruning. */ partindexes = prune_append_rel_partitions(parentrel); + parentrel->live_parts = partindexes; /* Must expand PlannerInfo arrays before we can add children. */ if (bms_num_members(partindexes) > 0) diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index d12d047002..059ab8f35e 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1757,6 +1757,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 9bb472fd98..088b191bdd 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -441,29 +441,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 01a664d246..d27611c2c7 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -733,6 +733,10 @@ typedef struct RelOptInfo Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Indexes into part_rels of the non-NULL + * RelOptInfos of unpruned partitions; exists + * to avoid having to iterate over the entire + * part_rels array to filter NULL entries. */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.11.0
v16-0004-Do-not-lock-all-partitions-at-the-beginning.patch (text/plain, 1.7K) download From 33b554820f8f589e2f117c096b525b013cf2954c Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 12 Oct 2018 19:12:53 +0900 Subject: [PATCH v16 4/4] Do not lock all partitions at the beginning --- src/backend/optimizer/util/inherit.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index d6f120fcc1..426d64faea 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -328,10 +328,6 @@ expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, * * Partitions are added to the query in order in which they are found in * the parent's PartitionDesc. - * - * Note: even though only the unpruned partitions will be added to the - * resulting plan, this still locks *all* partitions via find_all_inheritors - * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, @@ -345,10 +341,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, parentrel->inh_root_parent : parentRTindex; - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - /* * Initialize partitioned_child_rels to contain this RT index. * @@ -385,8 +377,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, RangeTblEntry *childrte; Index childRTindex; - /* Already locked above. */ - newrelation = heap_open(childOID, NoLock); + newrelation = heap_open(childOID, lockmode); Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-21 14:40  Jesper Pedersen <jesper.pedersen@redhat.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 0 replies; 265+ messages in thread

From: Jesper Pedersen @ 2019-01-21 14:40 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Rowley <david.rowley@2ndquadrant.com>; Amit Langote <amitlangote09@gmail.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers

Hi,

On 1/21/19 4:01 AM, Amit Langote wrote:
> Rebased again due to 8d8dcead1295.
> 

Passes check-world on 8d8dcead1295.

I ran a work load on the series, measuring the time to load the data 
plus the run-time to complete the work load.

           Load    Run
master    3m39s   144s
1778      3m12s    36s
NP [*]    2m20s    11s

[*] Non partitioned case.

Best regards,
  Jesper




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-01-22 02:32  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: David Rowley <david.rowley@2ndquadrant.com>
  0 siblings, 1 reply; 265+ messages in thread

From: Imai, Yoshikazu @ 2019-01-22 02:32 UTC (permalink / raw)
  To: David Rowley <david.rowley@2ndquadrant.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

I measured the latency of queries executed before and after creating generic plan with master + v15-patch.

In this test, table is partitioned into 4k partitions.
I executed 400,0001 queries by pgbench.
I changed the timing of creating generic plan at 1st, 10,001st, 20,001st, 50,001st, ..., 390,001st by changing the source code.
I run the test with setting both plan_cache_mode = auto and plan_cache_mode = force_custom_plan.

The results is below.
The value in before columns is showing the average latency of queries before creating generic plan in microsec. 
The value in after columns is showing the average latency of queries after creating generic plan in microsec.

[auto]
time of creating generic plan  | before[usec]  |  after[usec]
                          1st             531             142
                     10,001st             144             141
                     20,001st             141             144
                     50,001st             133             140
                    200,001st             131             143
                    390,001st             130             138


[force_custom_plan]
time of creating generic plan  | before[usec]  |  after[usec]
                          1st            
                     10,001st             144             129
                     20,001st             137             131
                     50,001st             133             134
                    200,001st             131             133
                    390,001st             132             131

* A generic plan is actually not created with plan_cache_mode = force_custom_plan.


Looking at the results of force_custom_plan, the latency of first 10,000 transactions is 144 microsec, and the latency of first 50,000 transactions is 133 microsec. 
I think that is because in the early transactions, relcache hash table is not hot (as David mentioned?).

Comparing the latencies in after column between auto and force_custom_plan, auto ones are higher about 8% than force_custom_plan ones.
That is, it seems creating generic plan affects the latency of queries executed after creating generic plan.


On Mon, Jan 21, 2019 at 1:32 AM, David Rowley wrote:
> It would be interesting to see the profiles of having the generic plan
> being built on the 6th execution vs the 400,000th execution.
> 
> I'd thought maybe one difference would be the relcache hash table
> having been expanded greatly after the generic plan was created

Does it mean that in the executing queries after the generic plan was created, the time of searching entry in the relcache hash table becomes slow and it increases the latency?

> but I
> see even the generic plan is selecting a random partition, so the
> cache would have ended up with that many items eventually anyway, and
> since we're talking in the odds of 7.8k TPS with 4k partitions, it
> would have only have taken about 2-3 seconds out of the 60 second run
> to hit most or all of those partitions anyway.

And does it mean even if we executes a lot of custom plan without creating generic plan, cache would have been ended up to the same size of which is after creating generic plan?


Anyway, I'll check the relcache size.
Since I don't know how to get profile at the just time of building generic plan, I'll use MemoryContextStats(MemoryContext*) function to check the relcache size at before/after building generic plan and at after executing a lot of custom plans.



Yoshikazu Imai



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-01-22 05:58  Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  0 siblings, 1 reply; 265+ messages in thread

From: Tsunakawa, Takayuki @ 2019-01-22 05:58 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; David Rowley <david.rowley@2ndquadrant.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Imai-san,

If the time for EXECUTE differs cleary before and after the creation of the generic plan, why don't you try to count the function calls for each EXECUTE?  Although I haven't tried it, but you can probably do it with SystemTap, like this:

probe process("your_postgres_path").function("*").call {
    /* accumulate the call count in an associative array */
}

Then, sort the functions by their call counts.  You may find some notable difference between the 5th and 7th EXECUTE.


Regards
Takayuki Tsunakawa



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-01-22 07:00  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
  0 siblings, 1 reply; 265+ messages in thread

From: Imai, Yoshikazu @ 2019-01-22 07:00 UTC (permalink / raw)
  To: Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>; David Rowley <david.rowley@2ndquadrant.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Tsunakawa-san

Thanks for giving the information.
I didn't use it yet, but I just used perf to clarify the difference of before and after the creation of the generic plan, and I noticed that usage of hash_seq_search() is increased about 3% in EXECUTE queries after the creation of the generic plan.

What I understand so far is about 10,000 while loops at total (4098+4098+some extra) is needed in hash_seq_search() in EXECUTE query after the creation of the generic plan.
10,000 while loops takes about 10 microsec (of course, we can't estimate correct time), and the difference of the latency between 5th and 7th EXECUTE is about 8 microsec, I currently think this causes the difference.

I don't know this problem relates to Amit-san's patch, but I'll continue to investigate it.

Yoshikazu Imai



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-22 09:47  David Rowley <david.rowley@2ndquadrant.com>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  0 siblings, 2 replies; 265+ messages in thread

From: David Rowley @ 2019-01-22 09:47 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On Tue, 22 Jan 2019 at 20:01, Imai, Yoshikazu
<imai.yoshikazu@jp.fujitsu.com> wrote:
> I didn't use it yet, but I just used perf to clarify the difference of before and after the creation of the generic plan, and I noticed that usage of hash_seq_search() is increased about 3% in EXECUTE queries after the creation of the generic plan.
>
> What I understand so far is about 10,000 while loops at total (4098+4098+some extra) is needed in hash_seq_search() in EXECUTE query after the creation of the generic plan.
> 10,000 while loops takes about 10 microsec (of course, we can't estimate correct time), and the difference of the latency between 5th and 7th EXECUTE is about 8 microsec, I currently think this causes the difference.
 >
> I don't know this problem relates to Amit-san's patch, but I'll continue to investigate it.

I had another thought... when you're making a custom plan you're only
grabbing locks on partitions that were not pruned (just 1 partition in
your case), but when making the generic plan, locks will be acquired
on all partitions (all 4000 of them). This likely means that when
building the generic plan for the first time that the
LockMethodLocalHash table is expanded to fit all those locks, and
since we never shrink those down again, it'll remain that size for the
rest of your run.  I imagine the reason for the slowdown is that
during LockReleaseAll(), a sequential scan is performed over the
entire hash table. I see from looking at the hash_seq_search() code
that the value of max_bucket is pretty critical to how it'll perform.
The while ((curElem = segp[segment_ndx]) == NULL) loop will need to
run fewer times with a lower max_bucket.

I just did a quick and crude test by throwing the following line into
the end of hash_seq_init():

elog(NOTICE, "%s %u", hashp->tabname, hashp->hctl->max_bucket);

With a 5000 partition table I see:

postgres=# set plan_cache_mode = 'auto';
postgres=# prepare q1 (int) as select * from listp where a = $1;
postgres=# explain analyze execute q1(1);
NOTICE:  Portal hash 15
NOTICE:  LOCALLOCK hash 15
NOTICE:  LOCALLOCK hash 15
...
<skip forward to the 6th execution>

postgres=# explain analyze execute q1(1);
NOTICE:  LOCALLOCK hash 5002
NOTICE:  Portal hash 15
NOTICE:  LOCALLOCK hash 5002
NOTICE:  LOCALLOCK hash 5002
                                               QUERY PLAN
--------------------------------------------------------------------------------------------------------
 Append  (cost=0.00..41.94 rows=13 width=4) (actual time=0.005..0.005
rows=0 loops=1)
   ->  Seq Scan on listp1  (cost=0.00..41.88 rows=13 width=4) (actual
time=0.005..0.005 rows=0 loops=1)
         Filter: (a = 1)
 Planning Time: 440.822 ms
 Execution Time: 0.017 ms
(5 rows)

I've not gone as far to try to recreate the performance drop you've
mentioned but I believe there's a very high chance that this is the
cause, and if so it's not for Amit to do anything with on this patch.

-- 
 David Rowley                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-22 13:31  今井 良一 <yoshikazu_i443@live.jp>
  parent: David Rowley <david.rowley@2ndquadrant.com>
  1 sibling, 0 replies; 265+ messages in thread

From: 今井 良一 @ 2019-01-22 13:31 UTC (permalink / raw)
  To: David Rowley <david.rowley@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On 2019/01/22 09:47, David Rowley wrote:
> On Tue, 22 Jan 2019 at 20:01, Imai, Yoshikazu
> <imai.yoshikazu@jp.fujitsu.com> wrote:
>> I didn't use it yet, but I just used perf to clarify the difference of before and after the creation of the generic plan, and I noticed that usage of hash_seq_search() is increased about 3% in EXECUTE queries after the creation of the generic plan.
>>
>> What I understand so far is about 10,000 while loops at total (4098+4098+some extra) is needed in hash_seq_search() in EXECUTE query after the creation of the generic plan.
>> 10,000 while loops takes about 10 microsec (of course, we can't estimate correct time), and the difference of the latency between 5th and 7th EXECUTE is about 8 microsec, I currently think this causes the difference.
>   >
>> I don't know this problem relates to Amit-san's patch, but I'll continue to investigate it.
> 
> I had another thought... when you're making a custom plan you're only
> grabbing locks on partitions that were not pruned (just 1 partition in
> your case), but when making the generic plan, locks will be acquired
> on all partitions (all 4000 of them). This likely means that when
> building the generic plan for the first time that the
> LockMethodLocalHash table is expanded to fit all those locks, and
> since we never shrink those down again, it'll remain that size for the
> rest of your run.  I imagine the reason for the slowdown is that
> during LockReleaseAll(), a sequential scan is performed over the > entire hash table. I see from looking at the hash_seq_search() code
> that the value of max_bucket is pretty critical to how it'll perform.
> The while ((curElem = segp[segment_ndx]) == NULL) loop will need to
> run fewer times with a lower max_bucket.

Really thanks to giving the detail explanation.
Yes, 10,000 while loops I mentioned is exactly scanning entire hash 
table in hash_seq_search() which is called in LockReleaseAll().

And I also checked expand_table() is called during building the generic 
plan which increases max_bucket.

> 
> I just did a quick and crude test by throwing the following line into
> the end of hash_seq_init():
> 
> elog(NOTICE, "%s %u", hashp->tabname, hashp->hctl->max_bucket);
> 
> With a 5000 partition table I see:
> 
> postgres=# set plan_cache_mode = 'auto';
> postgres=# prepare q1 (int) as select * from listp where a = $1;
> postgres=# explain analyze execute q1(1);
> NOTICE:  Portal hash 15
> NOTICE:  LOCALLOCK hash 15
> NOTICE:  LOCALLOCK hash 15
> ...
> <skip forward to the 6th execution>
> 
> postgres=# explain analyze execute q1(1);
> NOTICE:  LOCALLOCK hash 5002
> NOTICE:  Portal hash 15
> NOTICE:  LOCALLOCK hash 5002
> NOTICE:  LOCALLOCK hash 5002
>                                                 QUERY PLAN
> --------------------------------------------------------------------------------------------------------
>   Append  (cost=0.00..41.94 rows=13 width=4) (actual time=0.005..0.005
> rows=0 loops=1)
>     ->  Seq Scan on listp1  (cost=0.00..41.88 rows=13 width=4) (actual
> time=0.005..0.005 rows=0 loops=1)
>           Filter: (a = 1)
>   Planning Time: 440.822 ms
>   Execution Time: 0.017 ms
> (5 rows)
> 
> I've not gone as far to try to recreate the performance drop you've
> mentioned but I believe there's a very high chance that this is the
> cause, and if so it's not for Amit to do anything with on this patch.

Since expand_table() is called if we need locks more than max_bucket, I 
think the performance would also drop after executing the query like 
"select * from listp where a >= 1 and a <= 5000", but I've not tried it 
neither.

 > cause, and if so it's not for Amit to do anything with on this patch.

+1


One concern I have is force_custom_plan is slower than auto in master, 
which is opposite result in patched. I am investigating about it, but 
its causes would be also in another place from Amit's patch.


Thanks
--
Yoshikazu Imai


^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-23 01:21  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: David Rowley <david.rowley@2ndquadrant.com>
  1 sibling, 1 reply; 265+ messages in thread

From: Amit Langote @ 2019-01-23 01:21 UTC (permalink / raw)
  To: David Rowley <david.rowley@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On 2019/01/22 18:47, David Rowley wrote:
> On Tue, 22 Jan 2019 at 20:01, Imai, Yoshikazu
>> What I understand so far is about 10,000 while loops at total (4098+4098+some extra) is needed in hash_seq_search() in EXECUTE query after the creation of the generic plan.
>> 10,000 while loops takes about 10 microsec (of course, we can't estimate correct time), and the difference of the latency between 5th and 7th EXECUTE is about 8 microsec, I currently think this causes the difference.
>  >
>> I don't know this problem relates to Amit-san's patch, but I'll continue to investigate it.
> 
> I had another thought... when you're making a custom plan you're only
> grabbing locks on partitions that were not pruned (just 1 partition in
> your case), but when making the generic plan, locks will be acquired
> on all partitions (all 4000 of them). This likely means that when
> building the generic plan for the first time that the
> LockMethodLocalHash table is expanded to fit all those locks, and
> since we never shrink those down again, it'll remain that size for the
> rest of your run.  I imagine the reason for the slowdown is that
> during LockReleaseAll(), a sequential scan is performed over the
> entire hash table. I see from looking at the hash_seq_search() code
> that the value of max_bucket is pretty critical to how it'll perform.
> The while ((curElem = segp[segment_ndx]) == NULL) loop will need to
> run fewer times with a lower max_bucket.

I too think that that might be behind that slight drop in performance.
So, it's good to know what one of the performance bottlenecks is when
dealing with large number of relations in queries.

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-23 01:35  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 1 reply; 265+ messages in thread

From: Amit Langote @ 2019-01-23 01:35 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Amit Langote <amitlangote09@gmail.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers

On 2019/01/21 18:01, Amit Langote wrote:
> On 2019/01/17 11:17, Amit Langote wrote:
>> I have rebased the patches.  Other than rebasing, I've squashed the patch
>> to add inh_root_parent field to RelOptInfo which contained no other code
>> changes to use that field into the patch that contains changes to start
>> using it.  Updated the commit message of lazy-partition-initialization
>> patch to be accurate (it contained old details that have changed since I
>> first wrote that commit message).
> 
> Rebased again due to 8d8dcead1295.

Rebased due to the heap_open/close() -> table_open/close() change.

Thanks,
Amit

Attachments: download all (4 files, .tar.gz)

v17-0001-Overhaul-inheritance-update-delete-planning.patch (text/plain, 75.6K) download From bf206ce9cd9e3177ed7f6f6e4c1d9d0d67e91939 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 11:18:12 +0900 Subject: [PATCH v17 1/4] Overhaul inheritance update/delete planning With the current method, inheritance_planner, which handles the planning for update/delete commands targeting inheritance trees, repeatedly calls query_planner for each child table. That's inefficient for two reasons: 1. partprune.c cannot be used to perform partition pruning, because it can only be invoked from query_planner. With the current approach, query_planner only sees the individual partitions in the query tree it receives, not the parent partitioned table. This leaves each partition to be pruned using constraint exclusion. (constraint exclusion cannon prune hash partitions which means UPDATE/DELETE don't support hash partition pruning.) 2. Repeated invocation of query_planner results in allocating large amounts of memory, especially if there are many child tables. Also, it involves repeatedly performing the same processing, such as jointree processing. This commit addresses both of the issues by modifying query_planner to handle the inheritance target case directly. Instead of having the caller create separate PlannerInfo's for individual child target relations and pass them to query_planner (actually, via grouping_planner), this teaches query_planner itself to create the child PlannerInfo's by the original parent PlannerInfo. It creates them *after* it has generated enough state in the latter, such as RelOptInfos for various relations, EquivalenceClasses after processing the join tree, and *before* generating the scan and join paths. Scan paths are generated normally, except that Paths of target children are not "appended". Join paths are generated separately for each child target relation, that is, by replacing the parent relation references in the original join tree with the child relation references. Joinrels (RelOptInfos thereof) corresponding to various child target relation are saved in the parent PlannerInfo for further processing. inheritance_planner now calls query_planner just once at the beginning with the original unmodified query, which takes care of generating top-level join rels for the child queries as described above. For each child join rel, it then calls grouping_planner using previously created child PlannerInfo to finish up the paths such that they produce query's top-level target list expanded according to a given child relation's descriptor. grouping_planner()'s interface is modified so that we can pass it what's called a 'planned_rel', a RelOptInfo that already contains the necessary paths needed to produce its output. This removes some existing code in inheritance_planner that dealt with any subquery RTEs in the query. The rationale of that code was that the subquery RTEs may change during each iteration of planning (that is, for different children), so different iterations better use different copies of those RTEs. That was handled by making fresh copies of those RTEs for each iteration of planning which were appended to the range table, accompanied by modifying all expressions and auxiliary structures that referenced the original subquery RTEs to instead reference the copies (that is, change the varnos). This copying meant we would end up adding S * N new entries to the original range table by the time we got to the last unpruned child, where S is the number of subquery RTEs in the original query and N the number of unpruned children. Since with the new code we perform planning just once, I think we don't need this special handling. Actually, there is a regression test output change due to no longer having copies of subquery RTEs (see the diff of partition_join.out file.) Also we no longer need the hack in relation_excluded_by_constraints() involving setting PlannerInfo.inhTargetKind to enable constraint exclusion for target child relation, which is no longer needed. Constraint exclusion now runs during query_planner step described above. Regression test output change in partition_join.out is due to the fact that we no longer create duplicates of subquery RTEs in child range tables. --- doc/src/sgml/ddl.sgml | 17 +- src/backend/nodes/outfuncs.c | 2 +- src/backend/optimizer/path/allpaths.c | 613 +++++++++++++++++++++++++-- src/backend/optimizer/path/equivclass.c | 57 ++- src/backend/optimizer/path/joinrels.c | 2 +- src/backend/optimizer/plan/createplan.c | 10 - src/backend/optimizer/plan/planmain.c | 48 ++- src/backend/optimizer/plan/planner.c | 444 ++++++------------- src/backend/optimizer/prep/prepjointree.c | 1 - src/backend/optimizer/util/appendinfo.c | 33 +- src/backend/optimizer/util/inherit.c | 109 +++++ src/backend/optimizer/util/plancat.c | 73 +--- src/include/nodes/relation.h | 37 +- src/include/optimizer/inherit.h | 1 + src/include/optimizer/paths.h | 3 +- src/test/regress/expected/partition_join.out | 4 +- 16 files changed, 988 insertions(+), 466 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 85e4358988..1bb5ca6b08 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -4439,24 +4439,11 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <note> <para> - Currently, pruning of partitions during the planning of an - <command>UPDATE</command> or <command>DELETE</command> command is - implemented using the constraint exclusion method (however, it is - controlled by the <literal>enable_partition_pruning</literal> rather than - <literal>constraint_exclusion</literal>) &mdash; see the following section - for details and caveats that apply. - </para> - - <para> Execution-time partition pruning currently only occurs for the <literal>Append</literal> and <literal>MergeAppend</literal> node types. It is not yet implemented for the <literal>ModifyTable</literal> node - type. - </para> - - <para> - Both of these behaviors are likely to be changed in a future release - of <productname>PostgreSQL</productname>. + type, but that is likely to be changed in a future release of + <productname>PostgreSQL</productname>. </para> </note> </sect2> diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 0fde876c77..158da68925 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2210,7 +2210,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); WRITE_UINT_FIELD(qual_security_level); - WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind); WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasDeletedRTEs); @@ -2221,6 +2220,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_BITMAPSET_FIELD(curOuterRels); WRITE_NODE_FIELD(curOuterParams); WRITE_BOOL_FIELD(partColsUpdated); + WRITE_BOOL_FIELD(inherited_update); } static void diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index e1ecfd6052..61df22953a 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -38,6 +38,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" @@ -92,8 +93,12 @@ static void set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); +static void set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte); static void set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); +static void set_inherit_target_rel_pathlists(PlannerInfo *root, + RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, @@ -120,6 +125,8 @@ static void set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); +static void inheritance_make_rel_from_joinlist(PlannerInfo *root, + List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, pushdown_safety_info *safetyInfo); static bool recurse_pushdown_safe(Node *setOp, Query *topquery, @@ -150,27 +157,6 @@ make_one_rel(PlannerInfo *root, List *joinlist) Index rti; double total_pages; - /* - * Construct the all_baserels Relids set. - */ - root->all_baserels = NULL; - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - - /* there may be empty slots corresponding to non-baserel RTEs */ - if (brel == NULL) - continue; - - Assert(brel->relid == rti); /* sanity check on array */ - - /* ignore RTEs that are "other rels" */ - if (brel->reloptkind != RELOPT_BASEREL) - continue; - - root->all_baserels = bms_add_member(root->all_baserels, brel->relid); - } - /* Mark base rels as to whether we care about fast-start plans */ set_base_rel_consider_startup(root); @@ -218,13 +204,34 @@ make_one_rel(PlannerInfo *root, List *joinlist) /* * Generate access paths for the entire join tree. + * + * For UPDATE/DELETE on an inheritance parent, join paths should be + * generated for each child result rel separately. */ - rel = make_rel_from_joinlist(root, joinlist); + if (root->parse->resultRelation && + root->simple_rte_array[root->parse->resultRelation]->inh) + { + inheritance_make_rel_from_joinlist(root, joinlist); - /* - * The result should join all and only the query's base rels. - */ - Assert(bms_equal(rel->relids, root->all_baserels)); + /* + * Return the RelOptInfo of original target relation, although this + * doesn't really contain the final path. inheritance_planner + * from where we got here will generate the final path, but it will + * do so by iterative over child subroots, not through this + * RelOptInfo. + */ + rel = find_base_rel(root, root->parse->resultRelation); + } + else + { + rel = make_rel_from_joinlist(root, joinlist); + + /* + * The result should join all and only the query's base rels. + */ + Assert(bms_equal(rel->relids, root->all_baserels)); + + } return rel; } @@ -308,9 +315,10 @@ set_base_rel_sizes(PlannerInfo *root) * If parallelism is allowable for this query in general, see whether * it's allowable for this rel in particular. We have to do this * before set_rel_size(), because (a) if this rel is an inheritance - * parent, set_append_rel_size() will use and perhaps change the rel's - * consider_parallel flag, and (b) for some RTE types, set_rel_size() - * goes ahead and makes paths immediately. + * parent, set_append_rel_size() or set_inherit_target_rel_sizes() + * will use and perhaps change the rel's consider_parallel flag, and + * (b) for some RTE types, set_rel_size() goes ahead and makes paths + * immediately. */ if (root->glob->parallelModeOK) set_rel_consider_parallel(root, rel, rte); @@ -374,8 +382,15 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_size(root, rel, rti, rte); + /* + * If it's a target relation, set the sizes of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (rti == root->parse->resultRelation) + set_inherit_target_rel_sizes(root, rel, rti, rte); + else + set_append_rel_size(root, rel, rti, rte); } else { @@ -459,14 +474,26 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { + bool inherited_update = false; + if (IS_DUMMY_REL(rel)) { /* We already proved the relation empty, so nothing more to do */ } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_pathlist(root, rel, rti, rte); + /* + * If it's a target relation, set the pathlists of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (root->inherited_update && root->parse->resultRelation == rti) + { + inherited_update = true; + set_inherit_target_rel_pathlists(root, rel, rti, rte); + } + else + set_append_rel_pathlist(root, rel, rti, rte); } else { @@ -541,8 +568,12 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, if (set_rel_pathlist_hook) (*set_rel_pathlist_hook) (root, rel, rti, rte); - /* Now find the cheapest of the paths for this rel */ - set_cheapest(rel); + /* + * Now find the cheapest of the paths for this rel, unless it's an + * inheritance parent and this is an update/delete operation. + */ + if (!inherited_update) + set_cheapest(rel); #ifdef OPTIMIZER_DEBUG debug_print_rel(root, rel); @@ -904,6 +935,382 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) } /* + * set_inherit_target_rel_sizes + * Set size estimates for the child target relations + * + * The passed-in rel represents the target relation of the query that is + * known to have inheritance children. This is very much like + * set_append_rel_size, except it doesn't set the size estimates for the + * passed-in rel itself, because we don't need to "append" the children + * in this case. + */ +static void +set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + bool has_live_children; + ListCell *l; + Relids live_children = NULL; + bool did_pruning = false; + + /* Guard against stack overflow due to overly deep inheritance tree. */ + check_stack_depth(); + + Assert(IS_SIMPLE_REL(rel)); + + /* + * If the partitioned relation has any baserestrictinfo quals then we + * attempt to use these quals to prune away partitions that cannot + * possibly contain any tuples matching these quals. In this case we'll + * store the relids of all partitions which could possibly contain a + * matching tuple, and skip anything else in the loop below. + */ + if (enable_partition_pruning && + rte->relkind == RELKIND_PARTITIONED_TABLE && + rel->baserestrictinfo != NIL) + { + live_children = prune_append_rel_partitions(rel); + did_pruning = true; + } + + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + List *childquals; + Index cq_min_security; + bool have_const_false_cq; + PlannerInfo *subroot; + ListCell *lc; + List *translated_exprs, + *child_target_exprs; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + + /* + * The child rel's RelOptInfo was already created during + * add_base_rels_to_query. + */ + childrel = find_base_rel(root, childRTindex); + Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = NIL; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, + (Node *) rinfo->clause, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + /* track minimum security level among child quals */ + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might have + * securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals, see expand_inherited_rtentry().) Pull any such + * securityQuals up into the baserestrictinfo for the child. This is + * similar to process_security_barrier_quals() for the parent rel, + * except that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* not likely that we'd see constants here, so no check */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, false, false, + security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* + * OK, we've got all the baserestrictinfo quals for this child. + */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) + { + /* This partition was pruned; skip it. */ + set_dummy_rel_pathlist(childrel); + continue; + } + + if (relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* This child is excluded; skip it. */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Add missing Vars to child's reltarget. + * + * add_inherit_target_child_root() would've added only those that are + * needed to be present in the top-level tlist (or ones that + * preprocess_targetlist thinks are needed to be in the tlist.) We + * may need other attributes such as those contained in WHERE clauses, + * which are already computed for the parent during + * deconstruct_jointree processing of the original query (child's + * query never goes through deconstruct_jointree.) + */ + translated_exprs = (List *) + adjust_appendrel_attrs(root, + (Node *) rel->reltarget->exprs, + 1, &appinfo); + child_target_exprs = childrel->reltarget->exprs; + foreach(lc, translated_exprs) + { + Expr *expr = lfirst(lc); + + if (!list_member(child_target_exprs, expr)) + child_target_exprs = lappend(child_target_exprs, expr); + } + + subroot = root->inh_target_child_roots[childRTindex]; + + /* + * Also, We have to find any ECs containing parent's expressions and + * *replace* them with their copies containing child expressions. + */ + if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) + { + /* + * First make copies for ECs for the child's root, because some + * might be modified to replace parent EC expressions by child + * expressions in add_child_rel_equivalences. + */ + subroot->eq_classes = NIL; + foreach(lc, root->eq_classes) + { + EquivalenceClass *ec = lfirst(lc); + EquivalenceClass *new_ec = makeNode(EquivalenceClass); + + /* + * First memcpy which creates shallow copies of all + * the members and then make copies of members that could + * change. + * + * XXX comment in _copyPathKey says it's OK to recycle EC + * pointers, but as long as we do the whole planning for a + * given child using a given root, copying ECs like this + * shouldn't be a problem. Maybe, the following code could + * be in _copyEquivalenceClass()? + */ + memcpy(new_ec, ec, sizeof(EquivalenceClass)); + new_ec->ec_opfamilies = list_copy(ec->ec_opfamilies); + new_ec->ec_members = list_copy(ec->ec_members); + new_ec->ec_sources = list_copy(ec->ec_sources); + new_ec->ec_derives = list_copy(ec->ec_derives); + new_ec->ec_relids = bms_copy(ec->ec_relids); + + subroot->eq_classes = lappend(subroot->eq_classes, new_ec); + } + + add_child_rel_equivalences(subroot, appinfo, rel, childrel, true); + } + childrel->has_eclass_joins = rel->has_eclass_joins; + + /* Translate join quals. */ + childrel->joininfo = (List *) + adjust_appendrel_attrs(subroot, + (Node *) rel->joininfo, + 1, &appinfo); + + /* + * If parallelism is allowable for this query in general, see whether + * it's allowable for this childrel in particular. For consistency, + * do this before calling set_rel_size() for the child. + */ + if (root->glob->parallelModeOK) + set_rel_consider_parallel(subroot, childrel, childRTE); + + /* + * Compute the child's size using possibly modified subroot. + */ + set_rel_size(subroot, childrel, childRTindex, childRTE); + + /* If the child itself is partitioned it may turn into a dummy rel. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* We have at least one live child. */ + has_live_children = true; + + Assert(childrel->rows > 0); + } + + if (has_live_children) + { + /* + * Set a non-zero value here to cope with the caller's requirement + * that non-dummy relations are actually not empty. We don't try to + * be accurate here, because we're not going to create a path that + * combines the children outputs. + */ + rel->rows = 1; + } + else + { + /* + * All children were excluded by constraints, so mark the relation + * ass dummy. We must do this in this phase so that the rel's + * dummy-ness is visible when we generate paths for other rels. + */ + set_dummy_rel_pathlist(rel); + } +} + +/* + * set_inherit_target_rel_pathlists + * Build access paths for the child target relations + * + * Similar to set_append_rel_pathlist, except that we build paths of the + * children, but don't build an Append path. + */ +static void +set_inherit_target_rel_pathlists(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + ListCell *l; + + /* Nothing to do if all the children were excluded. */ + if (IS_DUMMY_REL(rel)) + return; + + /* Generate access paths for each of the children of passed-in rel */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + PlannerInfo *subroot; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + /* Re-locate the child RTE and RelOptInfo */ + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + childrel = root->simple_rel_array[childRTindex]; + subroot = root->inh_target_child_roots[childRTindex]; + /* Transfer the value from main root to subroot. */ + subroot->total_table_pages = root->total_table_pages; + + /* + * If set_append_rel_size() decided the parent appendrel was + * parallel-unsafe at some point after visiting this child rel, we + * need to propagate the unsafety marking down to the child, so that + * we don't generate useless partial paths for it. + */ + if (!rel->consider_parallel) + childrel->consider_parallel = false; + + /* + * Compute the child's access paths. + */ + set_rel_pathlist(subroot, childrel, childRTindex, childRTE); + } +} + +/* * set_append_rel_size * Set size estimates for a simple "append relation" * @@ -1184,7 +1591,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, * paths that produce those sort orderings). */ if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) - add_child_rel_equivalences(root, appinfo, rel, childrel); + add_child_rel_equivalences(root, appinfo, rel, childrel, false); childrel->has_eclass_joins = rel->has_eclass_joins; /* @@ -2625,6 +3032,140 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows) } /* + * inheritance_make_rel_from_joinlist + * Perform join planning for all non-dummy leaf inheritance children + * in their role as an UPDATE/DELETE query's target relation + * + * If a child relation is a partitioned table, its children are processed in + * turn by recursively calling this function. + */ +static void +inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; +#ifdef USE_ASSERT_CHECKING + Relids all_baserels; +#endif + + /* For UPDATE/DELETE queries, the top parent can only ever be a table. */ + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + Assert(planner_rt_fetch(resultRelation, root)->rtekind == RTE_RELATION); + + /* Nothing to do. */ + if (IS_DUMMY_REL(find_base_rel(root, resultRelation))) + return; + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + PlannerInfo *subroot; + RelOptInfo *childrel; + RelOptInfo *childjoinrel; + List *translated_joinlist; + + if (appinfo->parent_relid != resultRelation) + continue; + + childrel = find_base_rel(root, appinfo->child_relid); + + /* Ignore excluded/pruned children. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* Add this child. */ + root->inh_target_child_rels = lappend_int(root->inh_target_child_rels, + appinfo->child_relid); + + /* Perform join planning with child subroot. */ + subroot = root->inh_target_child_roots[appinfo->child_relid]; + Assert(subroot->parse->resultRelation > 0); + + /* + * Modify joinlist such that relations joined to the top parent rel + * appear to be joined to the child rel instead. Do the same for + * any SpecialJoinInfo structs. + */ + translated_joinlist = (List *) + adjust_appendrel_attrs(subroot, + (Node *) joinlist, + 1, &appinfo); + subroot->join_info_list = (List *) + adjust_appendrel_attrs(subroot, + (Node *) root->join_info_list, + 1, &appinfo); + + /* + * Sub-partitioned tables have to be processed recursively using the + * translated subroot as the parent, because AppendRelInfos link + * sub-partitions to their immediate parents, not the root partitioned + * table. + */ + if (childrel->part_scheme != NULL) + { + inheritance_make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * Add this child relation as a placeholder in the parent root's + * inh_target_child_joinrels so that inheritance_planner see same + * number of entries as inh_target_child_rels. + */ + root->inh_target_child_joinrels = + lappend(root->inh_target_child_joinrels, childrel); + + /* Also propagate this child's own children into parent's list. */ + if (subroot->inh_target_child_rels != NIL) + { + root->inh_target_child_rels = + list_concat(root->inh_target_child_rels, + subroot->inh_target_child_rels); + root->inh_target_child_joinrels = + list_concat(root->inh_target_child_joinrels, + subroot->inh_target_child_joinrels); + } + continue; + } + + /* + * Since we added the child rel directly into the join tree, we must + * modify it to be a "base" rel instead of an "other" rel, which the + * join planning code expects the relations being joined to be. + */ + childrel->reloptkind = RELOPT_BASEREL; + + Assert(subroot->join_rel_list == NIL); + Assert(subroot->join_rel_hash == NULL); + + /* Perform join planning and save the resulting RelOptInfo. */ + childjoinrel = make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * Remember this child target rel. inheritance_planner will perform + * the remaining steps of planning for each child relation separately. + * Specifically, it will call grouping_planner on every + * RelOptInfo contained in the inh_target_child_rels list, each of + * which represents the source of tuples to be modified for a given + * target child rel. + */ + root->inh_target_child_joinrels = + lappend(root->inh_target_child_joinrels, childjoinrel); +#ifdef USE_ASSERT_CHECKING + /* + * The following implements essentially the same Assert as in + * make_one_rel, our caller. + */ + all_baserels = bms_copy(root->all_baserels); + all_baserels = bms_del_member(all_baserels, + root->parse->resultRelation); + all_baserels = bms_add_member(all_baserels, + subroot->parse->resultRelation); + Assert(bms_equal(childjoinrel->relids, all_baserels)); +#endif + } +} + +/* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. * diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 6e134ae1d2..10136ef940 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -2104,12 +2104,16 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root, * * parent_rel and child_rel could be derived from appinfo, but since the * caller has already computed them, we might as well just pass them in. + * + * If 'replace' is true then the child EC members *replace* the corresponding + * parent members. */ void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, - RelOptInfo *child_rel) + RelOptInfo *child_rel, + bool replace) { ListCell *lc1; @@ -2117,14 +2121,23 @@ add_child_rel_equivalences(PlannerInfo *root, { EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc1); ListCell *lc2; + ListCell *prev; + ListCell *next; /* * If this EC contains a volatile expression, then generating child * EMs would be downright dangerous, so skip it. We rely on a - * volatile EC having only one EM. + * volatile EC having only one EM. If the caller asked to *replace* + * the original parent expression with the child one, then it's + * okay to proceed, because we wouldn't be adding another expression + * as being equivalent in that case. */ if (cur_ec->ec_has_volatile) - continue; + { + Assert(list_length(cur_ec->ec_members) == 1); + if (!replace) + continue; + } /* * No point in searching if parent rel not mentioned in eclass; but we @@ -2134,12 +2147,18 @@ add_child_rel_equivalences(PlannerInfo *root, !bms_is_subset(parent_rel->relids, cur_ec->ec_relids)) continue; - foreach(lc2, cur_ec->ec_members) + prev = NULL; + for (lc2 = list_head(cur_ec->ec_members); lc2 != NULL; lc2 = next) { EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2); + next = lnext(lc2); + if (cur_em->em_is_const) + { + prev = lc2; continue; /* ignore consts here */ + } /* Does it reference parent_rel? */ if (bms_overlap(cur_em->em_relids, parent_rel->relids)) @@ -2177,10 +2196,38 @@ add_child_rel_equivalences(PlannerInfo *root, child_rel->relids); } + /* + * Caller may have asked us to replace the parent expression + * with the child expression, so delete the parent expression + * first. + */ + if (replace) + cur_ec->ec_members = list_delete_cell(cur_ec->ec_members, + lc2, prev); + + /* + * If we're replacing, then the new member isn't really a + * a child, so em_is_child should be set to false. + */ (void) add_eq_member(cur_ec, child_expr, new_relids, new_nullable_relids, - true, cur_em->em_datatype); + !replace, cur_em->em_datatype); } + else + prev = lc2; + } + + if (replace) + { + /* + * Now fix up EC's relids set. It's OK to modify EC like this, + * because caller must have made a copy of the original EC. + * For example, see adjust_inherited_target_child_root. + */ + cur_ec->ec_relids = bms_difference(cur_ec->ec_relids, + parent_rel->relids); + cur_ec->ec_relids = bms_add_members(cur_ec->ec_relids, + child_rel->relids); } } } diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 8bfe9c3ff7..663130461a 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1463,7 +1463,7 @@ update_child_rel_info(PlannerInfo *root, /* Make child entries in the EquivalenceClass as well */ if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) - add_child_rel_equivalences(root, appinfo, rel, childrel); + add_child_rel_equivalences(root, appinfo, rel, childrel, false); childrel->has_eclass_joins = rel->has_eclass_joins; } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 97d0c28132..1e1c7efef9 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -2019,12 +2019,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) /* * During setrefs.c, we'll need the grouping_map to fix up the cols lists * in GroupingFunc nodes. Save it for setrefs.c to use. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be grouping in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->grouping_map == NULL); root->grouping_map = grouping_map; @@ -2181,12 +2176,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) * with InitPlan output params. (We can't just do that locally in the * MinMaxAgg node, because path nodes above here may have Agg references * as well.) Save the mmaggregates list to tell setrefs.c to do that. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be aggregates in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->minmax_aggs == NIL); root->minmax_aggs = best_path->mmaggregates; diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index fc97a1bb50..78557aeecb 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -28,7 +28,7 @@ #include "optimizer/paths.h" #include "optimizer/placeholder.h" #include "optimizer/planmain.h" - +#include "optimizer/prep.h" /* * query_planner @@ -59,6 +59,7 @@ query_planner(PlannerInfo *root, List *tlist, Query *parse = root->parse; List *joinlist; RelOptInfo *final_rel; + Index rti; /* * If the query has an empty join tree, then it's something easy like @@ -232,13 +233,52 @@ query_planner(PlannerInfo *root, List *tlist, extract_restriction_or_clauses(root); /* + * Construct the all_baserels Relids set. + */ + root->all_baserels = NULL; + for (rti = 1; rti < root->simple_rel_array_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + Assert(brel->relid == rti); /* sanity check on array */ + + /* ignore RTEs that are "other rels" */ + if (brel->reloptkind != RELOPT_BASEREL) + continue; + + root->all_baserels = bms_add_member(root->all_baserels, brel->relid); + } + + /* + * Add child subroots needed to use during planning for individual child + * targets + */ + if (root->inherited_update) + { + root->inh_target_child_roots = (PlannerInfo **) + palloc0(root->simple_rel_array_size * + sizeof(PlannerInfo *)); + add_inherited_target_child_roots(root); + } + + /* * Ready to do the primary planning. */ final_rel = make_one_rel(root, joinlist); - /* Check that we got at least one usable path */ - if (!final_rel || !final_rel->cheapest_total_path || - final_rel->cheapest_total_path->param_info != NULL) + /* + * Check that we got at least one usable path. In the case of an + * inherited update/delete operation, no path has been created for + * the query's actual target relation yet. + */ + if (!root->inherited_update && + (!final_rel || + !final_rel->cheapest_total_path || + final_rel->cheapest_total_path->param_info != NULL)) elog(ERROR, "failed to construct the join relation"); return final_rel; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 4465f002c8..fcb23a7ccd 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -39,6 +39,7 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "nodes/relation.h" #include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" @@ -131,7 +132,7 @@ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static void inheritance_planner(PlannerInfo *root); static void grouping_planner(PlannerInfo *root, bool inheritance_update, - double tuple_fraction); + RelOptInfo *planned_rel, double tuple_fraction); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, int *tleref_to_colnum_map); @@ -635,7 +636,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->grouping_map = NULL; root->minmax_aggs = NIL; root->qual_security_level = 0; - root->inhTargetKind = INHKIND_NONE; root->hasRecursion = hasRecursion; if (hasRecursion) root->wt_param_id = assign_special_exec_param(root); @@ -725,6 +725,13 @@ subquery_planner(PlannerGlobal *glob, Query *parse, expand_inherited_tables(root); /* + * Now that we have figured out "actual" inheritance situation of the + * relations, set whether the query is an inherited UPDATE/DELETE. + */ + root->inherited_update = (parse->resultRelation && + rt_fetch(parse->resultRelation, parse->rtable)->inh); + + /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -975,7 +982,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh) inheritance_planner(root); else - grouping_planner(root, false, tuple_fraction); + grouping_planner(root, false, NULL, tuple_fraction); /* * Capture the set of outer-level param IDs we have access to, for use in @@ -1151,13 +1158,41 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr) * inheritance set. * * We have to handle this case differently from cases where a source relation - * is an inheritance set. Source inheritance is expanded at the bottom of the - * plan tree (see allpaths.c), but target inheritance has to be expanded at - * the top. The reason is that for UPDATE, each target relation needs a - * different targetlist matching its own column set. Fortunately, + * is an inheritance set, where outputs of all the children are combined and + * combined output consumed using the source relation's (parent's) column set. + * For UPDATE, each target relation, where the query's output will go, needs + * a different targetlist matching its own column set. So, we will need to + * translate the query such that it produces the desired output required by + * each of the child target relations and plan each separately. Fortunately, * the UPDATE/DELETE target can never be the nullable side of an outer join, * so it's OK to generate the plan this way. * + * Planning steps that need to be performed separately for each child and + * any preliminary processing needed for that are handled by the following + * sub-routines of query_planner: + * + * add_inherit_target_roots - this creates copies of PlannerInfo for each + * child after query_planner has finished processing the join tree and + * creating EquivalenceClasses, so that working state of planning need + * not be created afresh for each child, especially the various arrays + * + * set_inherit_target_rel_sizes - this sets size estimates for child + * relations, replace the parent EC members by corresponding child ones + * in their respective subroots + * + * set_inherit_target_rel_pathlists - this creates Paths for scanning + * individual child relations + * + * inherit_make_rel_from_joinlist - this translates the jointree, replacing + * the target relation in the original jointree (the root parent) by + * individual child target relations and performs join planning on the + * resulting join tree, saving the RelOptInfos of resulting join relations + * into the top-level PlannerInfo + * + * Finally, here we apply grouping_planner to each child join relation + * produced by set_inherit_target_rel_pathlists to so that its path produces + * the desired targetlist. + * * Returns nothing; the useful output is in the Paths we attach to * the (UPPERREL_FINAL, NULL) upperrel stored in *root. * @@ -1169,14 +1204,8 @@ inheritance_planner(PlannerInfo *root) { Query *parse = root->parse; int top_parentRTindex = parse->resultRelation; - Bitmapset *subqueryRTindexes; - Bitmapset *modifiableARIindexes; int nominalRelation = -1; Index rootRelation = 0; - List *final_rtable = NIL; - int save_rel_array_size = 0; - RelOptInfo **save_rel_array = NULL; - AppendRelInfo **save_append_rel_array = NULL; List *subpaths = NIL; List *subroots = NIL; List *resultRelations = NIL; @@ -1184,70 +1213,47 @@ inheritance_planner(PlannerInfo *root) List *returningLists = NIL; List *rowMarks; RelOptInfo *final_rel; - ListCell *lc; - Index rti; + ListCell *lc1, + *lc2; RangeTblEntry *parent_rte; - PlannerInfo *parent_root; - Query *parent_parse; - Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); - PlannerInfo **parent_roots = NULL; + List *tlist; + standard_qp_extra qp_extra; + RelOptInfo *planned_rel; + /* Inheritance is never used for insert. */ Assert(parse->commandType != CMD_INSERT); /* - * We generate a modified instance of the original Query for each target - * relation, plan that, and put all the plans into a list that will be - * controlled by a single ModifyTable node. All the instances share the - * same rangetable, but each instance must have its own set of subquery - * RTEs within the finished rangetable because (1) they are likely to get - * scribbled on during planning, and (2) it's not inconceivable that - * subqueries could get planned differently in different cases. We need - * not create duplicate copies of other RTE kinds, in particular not the - * target relations, because they don't have either of those issues. Not - * having to duplicate the target relations is important because doing so - * (1) would result in a rangetable of length O(N^2) for N targets, with - * at least O(N^3) work expended here; and (2) would greatly complicate - * management of the rowMarks list. + * Let query_planner generate the access paths for the query for each + * target child relation. * - * To begin with, generate a bitmapset of the relids of the subquery RTEs. + * First, save the unexpanded version of the query's targetlist. + * create_inherit_target_child_root will use it as base when expanding + * it for a given child relation as the query's target relation instead + * of the parent. */ - subqueryRTindexes = NULL; - rti = 1; - foreach(lc, parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - if (rte->rtekind == RTE_SUBQUERY) - subqueryRTindexes = bms_add_member(subqueryRTindexes, rti); - rti++; - } + root->unexpanded_tlist = list_copy(root->parse->targetList); + tlist = preprocess_targetlist(root); + root->processed_tlist = tlist; + qp_extra.tlist = tlist; + qp_extra.activeWindows = NIL; + qp_extra.groupClause = NIL; + planned_rel = query_planner(root, tlist, standard_qp_callback, &qp_extra); /* - * Next, we want to identify which AppendRelInfo items contain references - * to any of the aforesaid subquery RTEs. These items will need to be - * copied and modified to adjust their subquery references; whereas the - * other ones need not be touched. It's worth being tense over this - * because we can usually avoid processing most of the AppendRelInfo - * items, thereby saving O(N^2) space and time when the target is a large - * inheritance tree. We can identify AppendRelInfo items by their - * child_relid, since that should be unique within the list. + * If it turned out during query planning that all the children are dummy + * (pruned or excluded by constraints), no need to do the steps below. + * Let grouping_planner finish up the final path. */ - modifiableARIindexes = NULL; - if (subqueryRTindexes != NULL) + if (IS_DUMMY_REL(planned_rel)) { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - - if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) || - bms_is_member(appinfo->child_relid, subqueryRTindexes) || - bms_overlap(pull_varnos((Node *) appinfo->translated_vars), - subqueryRTindexes)) - modifiableARIindexes = bms_add_member(modifiableARIindexes, - appinfo->child_relid); - } + grouping_planner(root, false, planned_rel, 0.0); + return; } + Assert(planned_rel->relid == top_parentRTindex); + Assert(planned_rel->reloptkind == RELOPT_BASEREL); + /* * If the parent RTE is a partitioned table, we should use that as the * nominal target relation, because the RTEs added for partitioned tables @@ -1255,7 +1261,7 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); + parent_rte = planner_rt_fetch(top_parentRTindex, root); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1263,95 +1269,39 @@ inheritance_planner(PlannerInfo *root) } /* - * The PlannerInfo for each child is obtained by translating the relevant - * members of the PlannerInfo for its immediate parent, which we find - * using the parent_relid in its AppendRelInfo. We save the PlannerInfo - * for each parent in an array indexed by relid for fast retrieval. Since - * the maximum number of parents is limited by the number of RTEs in the - * query, we use that number to allocate the array. An extra entry is - * needed since relids start from 1. + * Get on with finalizing the path for each child target relation by + * calling grouping_planner on its joinrel. */ - parent_roots = (PlannerInfo **) palloc0((list_length(parse->rtable) + 1) * - sizeof(PlannerInfo *)); - parent_roots[top_parentRTindex] = root; - - /* - * And now we can get on with generating a plan for each child table. - */ - foreach(lc, root->append_rel_list) + forboth(lc1, root->inh_target_child_rels, + lc2, root->inh_target_child_joinrels) { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - PlannerInfo *subroot; + Index childRTindex = lfirst_int(lc1); + RelOptInfo *childjoinrel = lfirst(lc2); + PlannerInfo *subroot = root->inh_target_child_roots[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; RangeTblEntry *child_rte; RelOptInfo *sub_final_rel; Path *subpath; - /* append_rel_list contains all append rels; ignore others */ - if (!bms_is_member(appinfo->parent_relid, parent_relids)) - continue; - - /* - * expand_inherited_rtentry() always processes a parent before any of - * that parent's children, so the parent_root for this relation should - * already be available. - */ - parent_root = parent_roots[appinfo->parent_relid]; - Assert(parent_root != NULL); - parent_parse = parent_root->parse; - - /* - * We need a working copy of the PlannerInfo so that we can control - * propagation of information back to the main copy. - */ - subroot = makeNode(PlannerInfo); - memcpy(subroot, parent_root, sizeof(PlannerInfo)); - - /* - * Generate modified query with this rel as target. We first apply - * adjust_appendrel_attrs, which copies the Query and changes - * references to the parent RTE to refer to the current child RTE, - * then fool around with subquery RTEs. - */ - subroot->parse = (Query *) - adjust_appendrel_attrs(parent_root, - (Node *) parent_parse, - 1, &appinfo); + Assert(subroot != NULL); + Assert(subroot->parse->resultRelation == childRTindex); /* * If there are securityQuals attached to the parent, move them to the * child rel (they've already been transformed properly for that). */ - parent_rte = rt_fetch(appinfo->parent_relid, subroot->parse->rtable); - child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable); + parent_rte = planner_rt_fetch(appinfo->parent_relid, subroot); + child_rte = planner_rt_fetch(appinfo->child_relid, subroot); child_rte->securityQuals = parent_rte->securityQuals; parent_rte->securityQuals = NIL; /* - * Mark whether we're planning a query to a partitioned table or an - * inheritance parent. + * Ignore a partitioned child. Instead, the paths of its children will + * be added to subpaths. */ - subroot->inhTargetKind = - (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED; - - /* - * If this child is further partitioned, remember it as a parent. - * Since a partitioned table does not have any data, we don't need to - * create a plan for it, and we can stop processing it here. We do, - * however, need to remember its modified PlannerInfo for use when - * processing its children, since we'll update their varnos based on - * the delta from immediate parent to child, not from top to child. - * - * Note: a very non-obvious point is that we have not yet added - * duplicate subquery RTEs to the subroot's rtable. We mustn't, - * because then its children would have two sets of duplicates, - * confusing matters. - */ - if (child_rte->inh) + if (childjoinrel->part_scheme) { - Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE); - parent_relids = bms_add_member(parent_relids, appinfo->child_relid); - parent_roots[appinfo->child_relid] = subroot; - + Assert(child_rte->inh); continue; } @@ -1379,111 +1329,8 @@ inheritance_planner(PlannerInfo *root) if (nominalRelation < 0) nominalRelation = appinfo->child_relid; - /* - * The rowMarks list might contain references to subquery RTEs, so - * make a copy that we can apply ChangeVarNodes to. (Fortunately, the - * executor doesn't need to see the modified copies --- we can just - * pass it the original rowMarks list.) - */ - subroot->rowMarks = copyObject(parent_root->rowMarks); - - /* - * The append_rel_list likewise might contain references to subquery - * RTEs (if any subqueries were flattenable UNION ALLs). So prepare - * to apply ChangeVarNodes to that, too. As explained above, we only - * want to copy items that actually contain such references; the rest - * can just get linked into the subroot's append_rel_list. - * - * If we know there are no such references, we can just use the outer - * append_rel_list unmodified. - */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - subroot->append_rel_list = NIL; - foreach(lc2, parent_root->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, modifiableARIindexes)) - appinfo2 = copyObject(appinfo2); - - subroot->append_rel_list = lappend(subroot->append_rel_list, - appinfo2); - } - } - - /* - * Add placeholders to the child Query's rangetable list to fill the - * RT indexes already reserved for subqueries in previous children. - * These won't be referenced, so there's no need to make them very - * valid-looking. - */ - while (list_length(subroot->parse->rtable) < list_length(final_rtable)) - subroot->parse->rtable = lappend(subroot->parse->rtable, - makeNode(RangeTblEntry)); - - /* - * If this isn't the first child Query, generate duplicates of all - * subquery RTEs, and adjust Var numbering to reference the - * duplicates. To simplify the loop logic, we scan the original rtable - * not the copy just made by adjust_appendrel_attrs; that should be OK - * since subquery RTEs couldn't contain any references to the target - * rel. - */ - if (final_rtable != NIL && subqueryRTindexes != NULL) - { - ListCell *lr; - - rti = 1; - foreach(lr, parent_parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lr); - - if (bms_is_member(rti, subqueryRTindexes)) - { - Index newrti; - - /* - * The RTE can't contain any references to its own RT - * index, except in its securityQuals, so we can save a - * few cycles by applying ChangeVarNodes to the rest of - * the rangetable before we append the RTE to it. - */ - newrti = list_length(subroot->parse->rtable) + 1; - ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); - ChangeVarNodes((Node *) subroot->rowMarks, rti, newrti, 0); - /* Skip processing unchanging parts of append_rel_list */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - foreach(lc2, subroot->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, - modifiableARIindexes)) - ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); - } - } - rte = copyObject(rte); - ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); - subroot->parse->rtable = lappend(subroot->parse->rtable, - rte); - } - rti++; - } - } - - /* There shouldn't be any OJ info to translate, as yet */ - Assert(subroot->join_info_list == NIL); - /* and we haven't created PlaceHolderInfos, either */ - Assert(subroot->placeholder_list == NIL); - - /* Generate Path(s) for accessing this result relation */ - grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ ); + /* Apply the top-level targetlist to childjoinrel's path. */ + grouping_planner(subroot, true, childjoinrel, 0.0); /* * Select cheapest path in case there's more than one. We always run @@ -1495,45 +1342,10 @@ inheritance_planner(PlannerInfo *root) subpath = sub_final_rel->cheapest_total_path; /* - * If this child rel was excluded by constraint exclusion, exclude it - * from the result plan. + * child rel cannot be empty, or inheritance_make_rel_from_joinlist + * wouldn't have put it in the list to begin with. */ - if (IS_DUMMY_PATH(subpath)) - continue; - - /* - * If this is the first non-excluded child, its post-planning rtable - * becomes the initial contents of final_rtable; otherwise, append - * just its modified subquery RTEs to final_rtable. - */ - if (final_rtable == NIL) - final_rtable = subroot->parse->rtable; - else - final_rtable = list_concat(final_rtable, - list_copy_tail(subroot->parse->rtable, - list_length(final_rtable))); - - /* - * We need to collect all the RelOptInfos from all child plans into - * the main PlannerInfo, since setrefs.c will need them. We use the - * last child's simple_rel_array (previous ones are too short), so we - * have to propagate forward the RelOptInfos that were already built - * in previous children. - */ - Assert(subroot->simple_rel_array_size >= save_rel_array_size); - for (rti = 1; rti < save_rel_array_size; rti++) - { - RelOptInfo *brel = save_rel_array[rti]; - - if (brel) - subroot->simple_rel_array[rti] = brel; - } - save_rel_array_size = subroot->simple_rel_array_size; - save_rel_array = subroot->simple_rel_array; - save_append_rel_array = subroot->append_rel_array; - - /* Make sure any initplans from this rel get into the outer list */ - root->init_plans = subroot->init_plans; + Assert(!IS_DUMMY_PATH(subpath)); /* Build list of sub-paths */ subpaths = lappend(subpaths, subpath); @@ -1565,36 +1377,6 @@ inheritance_planner(PlannerInfo *root) */ /* - * If we managed to exclude every child rel, return a dummy plan; it - * doesn't even need a ModifyTable node. - */ - if (subpaths == NIL) - { - set_dummy_rel_pathlist(final_rel); - return; - } - - /* - * Put back the final adjusted rtable into the master copy of the Query. - * (We mustn't do this if we found no non-excluded children.) - */ - parse->rtable = final_rtable; - root->simple_rel_array_size = save_rel_array_size; - root->simple_rel_array = save_rel_array; - root->append_rel_array = save_append_rel_array; - - /* Must reconstruct master's simple_rte_array, too */ - root->simple_rte_array = (RangeTblEntry **) - palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *)); - rti = 1; - foreach(lc, final_rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - root->simple_rte_array[rti++] = rte; - } - - /* * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will * have dealt with fetching non-locked marked rows, else we need to have * ModifyTable do that. @@ -1634,6 +1416,12 @@ inheritance_planner(PlannerInfo *root) * (inheritance_planner will create a single ModifyTable node covering all the * target tables.) * + * If non-NULL, planned_rel is a RelOptInfo containing paths for the query's + * top-level joinrel, which the caller produced by itself. In that case, this + * function only needs to adjust the targetlist of its cheapest_total_path. + * The only caller that may pass such a RelOptInfo currently is + * inheritance_planner. + * * tuple_fraction is the fraction of tuples we expect will be retrieved. * tuple_fraction is interpreted as follows: * 0: expect all tuples to be retrieved (normal case) @@ -1652,6 +1440,7 @@ inheritance_planner(PlannerInfo *root) */ static void grouping_planner(PlannerInfo *root, bool inheritance_update, + RelOptInfo *planned_rel, double tuple_fraction) { Query *parse = root->parse; @@ -1664,7 +1453,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, List *final_targets; List *final_targets_contain_srfs; bool final_target_parallel_safe; - RelOptInfo *current_rel; + RelOptInfo *current_rel = planned_rel; RelOptInfo *final_rel; ListCell *lc; @@ -1704,6 +1493,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * special work for recursive unions is the responsibility of * plan_set_operations. */ + Assert(current_rel == NULL); current_rel = plan_set_operations(root); /* @@ -1793,17 +1583,26 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, parse->groupClause = preprocess_groupclause(root, NIL); } - /* Preprocess targetlist */ - tlist = preprocess_targetlist(root); - /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation so - * that we can transfer its decoration (resnames etc) to the topmost - * tlist of the finished Plan. + * Preprocess targetlist, if needed. If the caller has already done + * query planning, root->processed_tlist already contains the desired + * targetlist. */ - root->processed_tlist = tlist; + if (planned_rel == NULL) + { + tlist = preprocess_targetlist(root); + + /* + * We are now done hacking up the query's targetlist. Most of the + * remaining planning work will be done with the PathTarget + * representation of tlists, but save aside the full representation + * so that we can transfer its decoration (resnames etc) to the + * topmost tlist of the finished Plan. + */ + root->processed_tlist = tlist; + } + else + tlist = root->processed_tlist; /* * Collect statistics about aggregates for estimating costs, and mark @@ -1883,8 +1682,9 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * We also generate (in standard_qp_callback) pathkey representations * of the query's sort clause, distinct clause, etc. */ - current_rel = query_planner(root, tlist, - standard_qp_callback, &qp_extra); + if (current_rel == NULL) + current_rel = query_planner(root, tlist, + standard_qp_callback, &qp_extra); /* * Convert the query's result tlist into PathTarget format. diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 77dbf4eba3..87adfa02b1 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -917,7 +917,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->grouping_map = NULL; subroot->minmax_aggs = NIL; subroot->qual_security_level = 0; - subroot->inhTargetKind = INHKIND_NONE; subroot->hasRecursion = false; subroot->wt_param_id = -1; subroot->non_recursive_path = NULL; diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index ca6622ece9..34a3ebbbe4 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -394,8 +394,39 @@ adjust_appendrel_attrs_mutator(Node *node, context->appinfos); return (Node *) phv; } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index eaf788e578..e5254bcb43 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -21,11 +21,14 @@ #include "miscadmin.h" #include "optimizer/appendinfo.h" #include "optimizer/inherit.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "utils/rel.h" +static PlannerInfo *create_inherited_target_child_root(PlannerInfo *root, + AppendRelInfo *appinfo); static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti); static void expand_partitioned_rtentry(PlannerInfo *root, @@ -492,3 +495,109 @@ translate_col_privs(const Bitmapset *parent_privs, return child_privs; } + +/* + * add_inherited_target_child_roots + * For each child of of the query's result relation, this translates the + * original query to match the child and creates a PlannerInfo containing + * the translated query + * + * Child PlannerInfo reuses most of the parent PlannerInfo's fields unchanged, + * except unexpanded_tlist, processed_tlist, and all_baserels, all of which + * are based on the child relation. + */ +void +add_inherited_target_child_roots(PlannerInfo *root) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; + + Assert(root->inh_target_child_roots != NULL); + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + RangeTblEntry *childRTE; + PlannerInfo *subroot; + + if (appinfo->parent_relid != resultRelation) + continue; + + /* + * Create a PlannerInfo for processing this child target relation + * with. + */ + subroot = create_inherited_target_child_root(root, appinfo); + root->inh_target_child_roots[appinfo->child_relid] = subroot; + + /* + * If the child is a partitioned table, recurse to do this for its + * partitions. + */ + childRTE = root->simple_rte_array[appinfo->child_relid]; + if (childRTE->inh) + add_inherited_target_child_roots(subroot); + } +} + +/* + * create_inherited_target_child_root + * Workhorse of add_inherited_target_child_roots + */ +static PlannerInfo * +create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) +{ + PlannerInfo *subroot; + List *tlist; + + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + + /* + * Translate the original query to replace Vars of the parent table + * by the corresponding Vars of the child table and to make child the main + * target relation of the query. + */ + subroot = makeNode(PlannerInfo); + memcpy(subroot, root, sizeof(PlannerInfo)); + + /* + * Restore the original, unexpanded targetlist, that is, the one before + * preprocess_targetlist was run on the original query. We'll run + * preprocess_targetlist after translating the query and the targetlist, + * so that it is expanded according to child's tuple descriptor. + */ + root->parse->targetList = root->unexpanded_tlist; + subroot->parse = (Query *) adjust_appendrel_attrs(root, + (Node *) root->parse, + 1, &appinfo); + + /* Save the just translated targetlist as unexpanded_tlist in the child's + * subroot, so that this child's own children can use it. Must use copy + * because subroot->parse->targetList will be modified soon. + */ + subroot->unexpanded_tlist = list_copy(subroot->parse->targetList); + + /* + * Apply planner's expansion of targetlist, such as adding various junk + * column, filling placeholder entries for dropped columns, etc., all of + * which occurs with the child's TupleDesc. + */ + tlist = preprocess_targetlist(subroot); + subroot->processed_tlist = tlist; + + /* Add any newly added Vars to the child RelOptInfo. */ + build_base_rel_tlists(subroot, tlist); + + /* + * Adjust all_baserels to replace the original target relation with the + * child target relation. Copy it before modifying though. + */ + subroot->all_baserels = bms_copy(root->all_baserels); + subroot->all_baserels = bms_del_member(subroot->all_baserels, + root->parse->resultRelation); + subroot->all_baserels = bms_add_member(subroot->all_baserels, + subroot->parse->resultRelation); + + return subroot; +} diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 261492e6b7..0a88c14483 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1264,36 +1264,6 @@ get_relation_constraints(PlannerInfo *root, } } - /* - * Append partition predicates, if any. - * - * For selects, partition pruning uses the parent table's partition bound - * descriptor, instead of constraint exclusion which is driven by the - * individual partition's partition constraint. - */ - if (enable_partition_pruning && root->parse->commandType != CMD_SELECT) - { - List *pcqual = RelationGetPartitionQual(relation); - - if (pcqual) - { - /* - * Run the partition quals through const-simplification similar to - * check constraints. We skip canonicalize_qual, though, because - * partition quals should be in canonical form already; also, - * since the qual is in implicit-AND format, we'd have to - * explicitly convert it to explicit-AND format and back again. - */ - pcqual = (List *) eval_const_expressions(root, (Node *) pcqual); - - /* Fix Vars to have the desired varno */ - if (varno != 1) - ChangeVarNodes((Node *) pcqual, 1, varno, 0); - - result = list_concat(result, pcqual); - } - } - table_close(relation, NoLock); return result; @@ -1417,40 +1387,15 @@ relation_excluded_by_constraints(PlannerInfo *root, /* * Skip further tests, depending on constraint_exclusion. */ - switch (constraint_exclusion) - { - case CONSTRAINT_EXCLUSION_OFF: - - /* - * Don't prune if feature turned off -- except if the relation is - * a partition. While partprune.c-style partition pruning is not - * yet in use for all cases (update/delete is not handled), it - * would be a UI horror to use different user-visible controls - * depending on such a volatile implementation detail. Therefore, - * for partitioned tables we use enable_partition_pruning to - * control this behavior. - */ - if (root->inhTargetKind == INHKIND_PARTITIONED) - break; - return false; - - case CONSTRAINT_EXCLUSION_PARTITION: - - /* - * When constraint_exclusion is set to 'partition' we only handle - * OTHER_MEMBER_RELs, or BASERELs in cases where the result target - * is an inheritance parent or a partitioned table. - */ - if ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) && - !(rel->reloptkind == RELOPT_BASEREL && - root->inhTargetKind != INHKIND_NONE && - rel->relid == root->parse->resultRelation)) - return false; - break; - - case CONSTRAINT_EXCLUSION_ON: - break; /* always try to exclude */ - } + if (constraint_exclusion == CONSTRAINT_EXCLUSION_OFF) + return false; + /* + * When constraint_exclusion is set to 'partition' we only handle + * OTHER_MEMBER_RELs. + */ + else if (constraint_exclusion == CONSTRAINT_EXCLUSION_PARTITION && + rel->reloptkind != RELOPT_OTHER_MEMBER_REL) + return false; /* * Check for self-contradictory restriction clauses. We dare not make diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 3430061361..beb2e323a8 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -319,9 +319,6 @@ typedef struct PlannerInfo Index qual_security_level; /* minimum security_level for quals */ /* Note: qual_security_level is zero if there are no securityQuals */ - InheritanceKind inhTargetKind; /* indicates if the target relation is an - * inheritance child or partition or a - * partitioned table */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */ bool hasDeletedRTEs; /* true if any RTE was deleted from jointree */ @@ -343,6 +340,40 @@ typedef struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* + * The following fields are set during query planning portion of an + * inherited UPDATE/DEELETE operation. + */ + bool inherited_update; /* UPDATE/DELETE on inheritance parent? */ + + /* + * This stores the original version of the query's targetlist that's + * not modified by the planner. + */ + List *unexpanded_tlist; + + /* + * Array containing simple_rel_array_size elements, indexed by rangetable + * index (entry 0 is wasted like simple_rel_array). Only elements + * corresponding to individual inheritance child target relations are + * non-NULL. Content of each PlannerInfo is same as the parent + * PlannerInfo, except for the parse tree which is a translated copy of + * the parent's parse tree. + */ + struct PlannerInfo **inh_target_child_roots; + + /* List of RT indexes of child RT indexes. */ + List *inh_target_child_rels; + + /* + * RelOptInfos corresponding to each child target rel. For leaf children, + * it's the RelOptInfo representing the output of make_rel_from_joinlist() + * called with the parent rel in the original join tree replaced by a + * given leaf child. For non-leaf children, it's the baserel RelOptInfo + * itself, left as a placeholder. + */ + List *inh_target_child_joinrels; } PlannerInfo; diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h index b2687abd4e..1dbc2ca5bf 100644 --- a/src/include/optimizer/inherit.h +++ b/src/include/optimizer/inherit.h @@ -18,5 +18,6 @@ extern void expand_inherited_tables(PlannerInfo *root); +extern void add_inherited_target_child_roots(PlannerInfo *root); #endif /* INHERIT_H */ diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 666217c189..f524ab6beb 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -161,7 +161,8 @@ extern EquivalenceClass *match_eclasses_to_foreign_key_col(PlannerInfo *root, extern void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, - RelOptInfo *child_rel); + RelOptInfo *child_rel, + bool replace); extern List *generate_implied_equalities_for_column(PlannerInfo *root, RelOptInfo *rel, ec_matches_callback_type callback, diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index c55de5d476..c9f02c1fb0 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1780,7 +1780,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_1 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_1 -> Nested Loop Semi Join @@ -1788,7 +1788,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_2 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_2 (28 rows) -- 2.11.0
v17-0002-Lazy-creation-of-RTEs-for-inheritance-children.patch (text/plain, 97.0K) download From c8f328dacf3dd9c24be2918fb9a80ce3cac8d168 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 26 Oct 2018 16:45:59 +0900 Subject: [PATCH v17 2/4] Lazy creation of RTEs for inheritance children Currently, expand_inherited_tables called from subquery_planner opens all inheritance child tables and adds them to the query's range table and PlannerInfo in the form of AppendRelInfo's initially and later RelOptInfo's. For partitioning, that's pretty wasteful, because it's possible to determine only the partitions that will need to be scanned using partition pruning, which doesn't require opening the partitions themselves. However, the point at which expand_inherited_tables is called currently is too early to perform partition pruning. This commit rearranges things within the planner so that expand_inherited_tables can be called from query_planner after the latter has finished setting up the information needed to perform partition pruning. However that means the PlannerInfo arrays that store RangeTblEntry's, RelOptInfo's, and AppendRelInfo's need to be expanded if inheritance expansion leads to adding more relations to planning. There are also consequences around targetlist expansion. For example, preprocess_targetlist cannot add the junk columns entries needed for row mark handling until all of the inheritance children have been determined, because different children may need different types of junk columns to be added. So, the approach of late inheritance expansion required some interface tweaks for preprocess_targetlist(). For partitioning, although we don't create a RangeTblEntry and RelOptInfo for pruned partitions at make_one_rel time, partitionwise join code relies on the fact that even though partitions may have been pruned, they'd still own a RelOptInfo to handle the outer join case where the pruned partition appears on the nullable side of join. Partitionwise join code deals with that by allocating dummy RelOptInfos for pruned partitions that are based mostly on their parent's properties. There are some regression test diffs: 1. Caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child, as seen in the partition_aggregate.out test output. 2. Those in postgres_fdw.out are caused by the fact that junk columns required for row marking are added to reltarget->exprs later than user columns, because the row marking junk columns aren't added until the inheritance is expanded which as of this commit is later than it used to be as noted above. --- contrib/postgres_fdw/expected/postgres_fdw.out | 32 +- src/backend/nodes/outfuncs.c | 1 + src/backend/optimizer/path/allpaths.c | 343 +-------- src/backend/optimizer/path/joinrels.c | 61 ++ src/backend/optimizer/plan/initsplan.c | 58 -- src/backend/optimizer/plan/planmain.c | 10 + src/backend/optimizer/plan/planner.c | 74 +- src/backend/optimizer/plan/setrefs.c | 6 + src/backend/optimizer/prep/preptlist.c | 15 +- src/backend/optimizer/prep/prepunion.c | 3 + src/backend/optimizer/util/appendinfo.c | 45 +- src/backend/optimizer/util/inherit.c | 817 +++++++++++++++------- src/backend/optimizer/util/plancat.c | 47 +- src/backend/optimizer/util/relnode.c | 107 +-- src/backend/partitioning/partprune.c | 43 +- src/include/nodes/relation.h | 18 + src/include/optimizer/appendinfo.h | 7 +- src/include/optimizer/pathnode.h | 1 + src/include/optimizer/plancat.h | 4 +- src/include/optimizer/prep.h | 3 +- src/include/partitioning/partprune.h | 2 +- src/test/regress/expected/partition_aggregate.out | 4 +- 22 files changed, 931 insertions(+), 770 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index bb92d9d37a..33a33606ef 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7072,15 +7072,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7110,15 +7110,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7147,15 +7147,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid @@ -7165,15 +7165,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (39 rows) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 158da68925..67697b8884 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2272,6 +2272,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_BOOL_FIELD(consider_partitionwise_join); WRITE_BITMAPSET_FIELD(top_parent_relids); WRITE_NODE_FIELD(partitioned_child_rels); + WRITE_UINT_FIELD(inh_root_parent); } static void diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 61df22953a..ab4f376e92 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -45,7 +45,6 @@ #include "optimizer/var.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -383,6 +382,14 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, else if (rte->inh) { /* + * expand_inherited_tables may have proved that the relation is empty. + * For example, if it's a partitioned table with 0 partitions or all + * of its partitions are pruned. In that case nothing to do here. + */ + if (IS_DUMMY_REL(rel)) + return; + + /* * If it's a target relation, set the sizes of children instead. * Otherwise, we'll append the outputs of children, so process it as * an "append relation". @@ -951,38 +958,18 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, int parentRTindex = rti; bool has_live_children; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); Assert(IS_SIMPLE_REL(rel)); - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - foreach(l, root->append_rel_list) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; PlannerInfo *subroot; ListCell *lc; List *translated_exprs, @@ -994,141 +981,22 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); + Assert(childrel != NULL); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals. */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); + if (IS_DUMMY_REL(childrel)) continue; - } - - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } if (relation_excluded_by_constraints(root, childrel, childRTE)) { @@ -1181,15 +1049,9 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, EquivalenceClass *new_ec = makeNode(EquivalenceClass); /* - * First memcpy which creates shallow copies of all - * the members and then make copies of members that could - * change. - * - * XXX comment in _copyPathKey says it's OK to recycle EC - * pointers, but as long as we do the whole planning for a - * given child using a given root, copying ECs like this - * shouldn't be a problem. Maybe, the following code could - * be in _copyEquivalenceClass()? + * First memcpy the original EC, which creates shallow copies + * of all the members and then make copies of members that + * might change during add_child_rel_equivalences(). */ memcpy(new_ec, ec, sizeof(EquivalenceClass)); new_ec->ec_opfamilies = list_copy(ec->ec_opfamilies); @@ -1332,8 +1194,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -1341,32 +1201,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Assert(IS_SIMPLE_REL(rel)); /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, we will bubble up - * the indexes of partitioned relations that appear down in the tree, so - * that when we've created Paths for all the children, the root - * partitioned table's list will contain all such indexes. - */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - rel->partitioned_child_rels = list_make1_int(rti); - - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - - /* * If this is a partitioned baserel, set the consider_partitionwise_join * flag; currently, we only consider partitionwise joins with the baserel * if its targetlist doesn't contain a whole-row Var. @@ -1403,12 +1237,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; ListCell *parentvars; ListCell *childvars; - ListCell *lc; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1416,142 +1246,22 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); + Assert(childrel != NULL); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); /* - * We have to copy the parent's targetlist and quals to the child, - * with appropriate substitution of variables. However, only the - * baserestrictinfo quals are needed before we can check for - * constraint exclusion; so do that first and then check to see if we - * can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals. */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); + if (IS_DUMMY_REL(childrel)) continue; - } - - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } if (relation_excluded_by_constraints(root, childrel, childRTE)) { @@ -1564,7 +1274,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, } /* - * CE failed, so finish copying/modifying targetlist and join quals. + * Copy/Modify targetlist. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -3069,6 +2779,7 @@ inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) continue; childrel = find_base_rel(root, appinfo->child_relid); + Assert(childrel != NULL); /* Ignore excluded/pruned children. */ if (IS_DUMMY_REL(childrel)) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 663130461a..bc89de9a9d 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -20,6 +20,7 @@ #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" +#include "optimizer/tlist.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -51,6 +52,9 @@ static SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, Relids left_relids, Relids right_relids); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + int partidx); /* @@ -1383,6 +1387,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (IS_SIMPLE_REL(rel1) && child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts); + if (IS_SIMPLE_REL(rel1) && child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts); + /* * If a child table has consider_partitionwise_join=false, it means * that it's a dummy relation for which we skipped setting up tlist @@ -1674,3 +1683,55 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +static RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx) +{ + RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + AppendRelInfo *appinfo = make_append_rel_info(parent, parentrte, + parent->tupdesc, + parentrte->relid, + parent->reltype, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index a66374094f..c628f3d9e7 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -616,64 +616,6 @@ create_lateral_join_info(PlannerInfo *root) bms_add_member(brel2->lateral_referencers, rti); } } - - /* - * Lastly, propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - */ - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - RangeTblEntry *brte = root->simple_rte_array[rti]; - - /* - * Skip empty slots. Also skip non-simple relations i.e. dead - * relations. - */ - if (brel == NULL || !IS_SIMPLE_REL(brel)) - continue; - - /* - * In the case of table inheritance, the parent RTE is directly linked - * to every child table via an AppendRelInfo. In the case of table - * partitioning, the inheritance hierarchy is expanded one level at a - * time rather than flattened. Therefore, an other member rel that is - * a partitioned table may have children of its own, and must - * therefore be marked with the appropriate lateral info so that those - * children eventually get marked also. - */ - Assert(brte); - if (brel->reloptkind == RELOPT_OTHER_MEMBER_REL && - (brte->rtekind != RTE_RELATION || - brte->relkind != RELKIND_PARTITIONED_TABLE)) - continue; - - if (brte->inh) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); - RelOptInfo *childrel; - - if (appinfo->parent_relid != rti) - continue; - childrel = root->simple_rel_array[appinfo->child_relid]; - Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - Assert(childrel->direct_lateral_relids == NULL); - childrel->direct_lateral_relids = brel->direct_lateral_relids; - Assert(childrel->lateral_relids == NULL); - childrel->lateral_relids = brel->lateral_relids; - Assert(childrel->lateral_referencers == NULL); - childrel->lateral_referencers = brel->lateral_referencers; - } - } - } } diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 78557aeecb..62e11157da 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -254,6 +254,16 @@ query_planner(PlannerInfo *root, List *tlist, } /* + * Expand RT entries that represent inherited or partitioned tables. + * This will perform partition pruning on partitioned tables in the + * original range table and also recursively on any child partitioned + * tables that were added by the expansion of the original parent(s). + * As new entries are added to the range table, various arrays in the + * PlannerInfo will be expanded accordingly. + */ + expand_inherited_tables(root); + + /* * Add child subroots needed to use during planning for individual child * targets */ diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index fcb23a7ccd..738ab0a37d 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -25,6 +25,7 @@ #include "access/table.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -704,27 +705,18 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } /* - * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). - */ - preprocess_rowmarks(root); - - /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain base relations not joins, so it's OK (and marginally more - * efficient) to do it after checking for join RTEs. We must do it after - * pulling up subqueries, else we'd fail to handle inherited tables in - * subqueries. - */ - expand_inherited_tables(root); - - /* * Now that we have figured out "actual" inheritance situation of the * relations, set whether the query is an inherited UPDATE/DELETE. */ @@ -732,6 +724,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh); /* + * Preprocess RowMark information. We need to do this after subquery + * pullup (so that all non-inherited RTEs are present). + */ + preprocess_rowmarks(root); + + /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -1233,7 +1231,9 @@ inheritance_planner(PlannerInfo *root) * of the parent. */ root->unexpanded_tlist = list_copy(root->parse->targetList); - tlist = preprocess_targetlist(root); + + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root, false); root->processed_tlist = tlist; qp_extra.tlist = tlist; qp_extra.activeWindows = NIL; @@ -1243,9 +1243,11 @@ inheritance_planner(PlannerInfo *root) /* * If it turned out during query planning that all the children are dummy * (pruned or excluded by constraints), no need to do the steps below. - * Let grouping_planner finish up the final path. + * For regular inheritance case, it's possible that we only need to modify + * the parent table. Let grouping_planner finish up the final path. */ - if (IS_DUMMY_REL(planned_rel)) + parent_rte = planner_rt_fetch(top_parentRTindex, root); + if (!parent_rte->inh || IS_DUMMY_REL(planned_rel)) { grouping_planner(root, false, planned_rel, 0.0); return; @@ -1261,7 +1263,6 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = planner_rt_fetch(top_parentRTindex, root); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1590,14 +1591,19 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, */ if (planned_rel == NULL) { - tlist = preprocess_targetlist(root); + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root, false); /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation - * so that we can transfer its decoration (resnames etc) to the - * topmost tlist of the finished Plan. + * We are now mostly done hacking up the query's targetlist. More + * columns might be added during query_planner after inheritance + * expansion in make_one_rel, because some children may require + * different column than the parent for row mark handling; see + * add_rowmark_junk_columns(). Most of the remaining planning + * work will be done with the PathTarget representation of tlists, + * but save aside the full representation so that we can transfer + * its decoration (resnames etc) to the topmost tlist of the + * finished Plan. */ root->processed_tlist = tlist; } @@ -2373,7 +2379,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2398,7 +2404,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } @@ -6835,6 +6841,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -6936,6 +6946,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 5d363edab8..4dcdede6b6 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -325,6 +325,12 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) UPPERREL_FINAL, NULL))) add_rtes_to_flat_rtable(rel->subroot, true); } + /* + * A NULL rel also means an unplanned subquery rte, so apply + * flatten_unplanned_rtes. + */ + else + flatten_unplanned_rtes(glob, rte); } rti++; } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 0e045f171a..ccd4f13b83 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -65,9 +65,13 @@ static List *expand_targetlist(List *tlist, int command_type, * * As a side effect, if there's an ON CONFLICT UPDATE clause, its targetlist * is also preprocessed (and updated in-place). + * + * inheritance_expanded specifies whether inheritance tables are expanded + * as of calling this function. Caller must specify it because that affects + * which row marking related junk columns get added to the targetlist. */ List * -preprocess_targetlist(PlannerInfo *root) +preprocess_targetlist(PlannerInfo *root, bool inheritance_expanded) { Query *parse = root->parse; int result_relation = parse->resultRelation; @@ -134,6 +138,15 @@ preprocess_targetlist(PlannerInfo *root) if (rc->rti != rc->prti) continue; + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which will change the parent row + * mark's allMarkTypes fields. + */ + if (rc->isParent && !inheritance_expanded) + continue; + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) { /* Need to fetch TID */ diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 55eeb5127c..d4d36248a1 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -31,13 +31,16 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" +#include "optimizer/var.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" #include "utils/lsyscache.h" diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index 34a3ebbbe4..d2c3c7b590 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -31,10 +31,10 @@ typedef struct AppendRelInfo **appinfos; } adjust_appendrel_attrs_context; -static void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, adjust_appendrel_attrs_context *context); static List *adjust_inherited_tlist(List *tlist, @@ -46,18 +46,20 @@ static List *adjust_inherited_tlist(List *tlist, * Build an AppendRelInfo for the parent-child pair */ AppendRelInfo * -make_append_rel_info(Relation parentrel, Relation childrel, - Index parentRTindex, Index childRTindex) +make_append_rel_info(RelOptInfo *parent, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childtype, + Index childRTindex) { AppendRelInfo *appinfo = makeNode(AppendRelInfo); - appinfo->parent_relid = parentRTindex; + appinfo->parent_relid = parent->relid; appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->rd_rel->reltype; - appinfo->child_reltype = childrel->rd_rel->reltype; - make_inh_translation_list(parentrel, childrel, childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = RelationGetRelid(parentrel); + appinfo->parent_reltype = parent->reltype; + appinfo->child_reltype = childtype; + make_inh_translation_list(parent->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; return appinfo; } @@ -70,14 +72,11 @@ make_append_rel_info(Relation parentrel, Relation childrel, * For paranoia's sake, we match type/collation as well as attribute name. */ static void -make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, - List **translated_vars) +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) { List *vars = NIL; - TupleDesc old_tupdesc = RelationGetDescr(oldrelation); - TupleDesc new_tupdesc = RelationGetDescr(newrelation); - Oid new_relid = RelationGetRelid(newrelation); int oldnatts = old_tupdesc->natts; int newnatts = new_tupdesc->natts; int old_attno; @@ -107,7 +106,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, * When we are generating the "translation list" for the parent table * of an inheritance set, no need to search for matches. */ - if (oldrelation == newrelation) + if (from_rel == to_rel) { vars = lappend(vars, makeVar(newvarno, (AttrNumber) (old_attno + 1), @@ -133,10 +132,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, { HeapTuple newtup; - newtup = SearchSysCacheAttName(new_relid, attname); + newtup = SearchSysCacheAttName(to_rel, attname); if (!newtup) elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; ReleaseSysCache(newtup); @@ -146,10 +145,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, /* Found it, check type and collation match */ if (atttypid != att->atttypid || atttypmod != att->atttypmod) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); if (attcollation != att->attcollation) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); vars = lappend(vars, makeVar(newvarno, (AttrNumber) (new_attno + 1), diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index e5254bcb43..71d9113ab2 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -18,33 +18,43 @@ #include "access/table.h" #include "catalog/partition.h" #include "catalog/pg_inherits.h" +#include "catalog/pg_type.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" #include "optimizer/inherit.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" +#include "optimizer/var.h" +#include "partitioning/partprune.h" #include "utils/rel.h" static PlannerInfo *create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo); -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); +static void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti); +static void expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, + RangeTblEntry *rte, Index rti, RelOptInfo *rel); static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); - +static RelOptInfo *build_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); /* * expand_inherited_tables @@ -52,37 +62,145 @@ static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, * into an "append relation". At the conclusion of this process, * the "inh" flag is set in all and only those RTEs that are append * relation parents. + * + * Note that although we're calling the combined output of inheritance set + * relations an "append relation" here, the caller may not always want to + * combine the relations. For example, if the parent of the inheritance + * set is the query's target relation, each child relation is processed + * on its own as the query's target relation. */ void expand_inherited_tables(PlannerInfo *root) { - Index nrtes; + int orig_rtable_size; Index rti; - ListCell *rl; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; /* * expand_inherited_rtentry may add RTEs to parse->rtable. The function is * expected to recursively handle any RTEs that it creates with inh=true. * So just scan as far as the original end of the rtable list. */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) + for (rti = 1; rti < orig_rtable_size; rti++) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + if (rte->inh) + expand_inherited_rtentry(root, brel, rte, rti); } } /* * expand_inherited_rtentry - * Check whether a rangetable entry represents an inheritance set. - * If so, add entries for all the child tables to the query's - * rangetable, and build AppendRelInfo nodes for all the child tables - * and add them to root->append_rel_list. If not, clear the entry's - * "inh" flag to prevent later code from looking for AppendRelInfos. + * This initializes RelOptInfos for inheritance child relations if the + * passed-in relation has any + * + * 'rel' is the parent relation, whose range table entry ('rte') has been + * marked to require adding children. Parent could either be a subquery (if + * we flattened UNION ALL query) or a table that's known to have (or once had) + * inheritance children. The latter consists of both regular inheritance + * parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as a child to be scanned as part of the + * inheritance set. The original RTE is considered to represent the whole + * inheritance set. + */ +static void +expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_inheritance_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_nonpartitioned_inherited_rtentry(root, rte, rti, rel); + } +} + +/* + * expand_nonpartitioned_inherited_rtentry + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. * * Note that the original RTE is considered to represent the whole * inheritance set. The first of the generated RTEs is an RTE for the same @@ -93,55 +211,35 @@ expand_inherited_tables(PlannerInfo *root) * regular inheritance, a parent RTE must always have at least two associated * AppendRelInfos: one corresponding to the parent table as a simple member of * inheritance set and one or more corresponding to the actual children. - * Since a partitioned table is not scanned, it might have only one associated - * AppendRelInfo. */ static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, + RangeTblEntry *rte, + Index rti, + RelOptInfo *rel) { Oid parentOID; PlanRowMark *oldrc; - Relation oldrelation; - LOCKMODE lockmode; + LOCKMODE lockmode = rte->rellockmode; List *inhOIDs; ListCell *l; + int num_children; + int num_children_added = 0; - /* Does RT entry allow inheritance? */ - if (!rte->inh) - return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } - /* Fast path for common case of childless table */ + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); parentOID = rte->relid; - if (!has_subclass(parentOID)) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * The rewriter should already have obtained an appropriate lock on each - * relation named in the query. However, for each child relation we add - * to the query, we must obtain an appropriate lock, because this will be - * the first use of those relations in the parse/rewrite/plan pipeline. - * Child rels should use the same lockmode as their parent. - */ - lockmode = rte->rellockmode; /* Scan for all members of inheritance set, acquire needed locks */ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); /* * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite above has_subclass() check, if table - * once had a child but no longer does. + * case. This could happen despite has_subclass() check performed by + * subquery_planner, if table once had a child but no longer does. */ - if (list_length(inhOIDs) < 2) + num_children = list_length(inhOIDs); + if (num_children < 2) { /* Clear flag before returning */ rte->inh = false; @@ -149,216 +247,221 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } /* - * If parent relation is selected FOR UPDATE/SHARE, we need to mark its - * PlanRowMark as isParent = true, and generate a new PlanRowMark for each - * child. + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. */ oldrc = get_plan_rowmark(root->rowMarks, rti); - if (oldrc) - oldrc->isParent = true; + Assert(oldrc == NULL || oldrc->isParent); /* - * Must open the parent relation to examine its tupdesc. We need not lock - * it; we assume the rewriter already did. + * Must expand PlannerInfo arrays by num_children before we can add + * children. */ - oldrelation = table_open(parentOID, NoLock); + Assert(num_children > 0); + expand_planner_arrays(root, num_children); - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) + foreach(l, inhOIDs) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); - - /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. - */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); - } - else - { - List *appinfos = NIL; + Oid childOID = lfirst_oid(l); + Relation newrelation; RangeTblEntry *childrte; Index childRTindex; + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. */ - foreach(l, inhOIDs) + if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = table_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - table_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - table_close(newrelation, NoLock); + table_close(newrelation, lockmode); + continue; } - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); + (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation, &childrte, + &childRTindex); + Assert(childrte != NULL); + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + Assert(childRTindex > 0); + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; } - table_close(oldrelation, NoLock); + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } } /* * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) + Index parentRTindex, RelOptInfo *parentrel) { + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; - check_stack_depth(); - - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + if (bms_num_members(partindexes) > 0) + expand_planner_arrays(root, bms_num_members(partindexes)); /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. */ - if (partdesc->nparts == 0) - { - parentrte->inh = false; - return; - } + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); - for (i = 0; i < partdesc->nparts; i++) + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) { - Oid childOID = partdesc->oids[i]; - Relation childrel; + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + RangeTblEntry *childrte; + Index childRTindex; /* Open rel; we already have required locks */ - childrel = table_open(childOID, NoLock); + newrelation = table_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation, + &childrte, &childRTindex); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + /* Close child relations, but keep locks */ + table_close(newrelation, NoLock); + + /* If the child is partitioned itself, expand it too. */ + if (childrel->part_scheme) + { + Assert(childrte->inh); expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); + childrel); + } + } - /* Close child relation, but keep locks */ - table_close(childrel, NoLock); + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); } } /* - * expand_single_inheritance_child - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) + * The return value is the RelOptInfo that's added. * * PlanRowMarks still carry the top-parent's RTI, and the top-parent's * allMarkTypes field still accumulates values from all descendents. * * "parentrte" and "parentRTindex" are immediate parent's RTE and * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. */ -static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); Oid childOID = RelationGetRelid(childrel); RangeTblEntry *childrte; Index childRTindex; AppendRelInfo *appinfo; + RelOptInfo *childrelopt; /* * Build an RTE for the child, and attach to query's rangetable list. We * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh = false. Also, set requiredPerms to zero since - * all required permissions checks are done on the original RTE. Likewise, - * set the child's securityQuals to empty, because we only want to apply - * the parent's RLS conditions regardless of what RLS properties + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties * individual children may have. (This is an intentional choice to make * inherited RLS work like regular permissions checks.) The parent * securityQuals will be propagated to children along with other base @@ -368,49 +471,42 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childrte_p = childrte; childrte->relid = childOID; childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) - childrte->inh = true; - else - childrte->inh = false; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); childrte->requiredPerms = 0; childrte->securityQuals = NIL; parse->rtable = lappend(parse->rtable, childrte); childRTindex = list_length(parse->rtable); *childRTindex_p = childRTindex; - /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. - */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) - { - appinfo = make_append_rel_info(parentrel, childrel, - parentRTindex, childRTindex); - *appinfos = lappend(*appinfos, appinfo); + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + RelationGetDescr(childrel), + RelationGetRelid(childrel), + RelationGetForm(childrel)->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); } /* @@ -442,6 +538,253 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need to create one, + * because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_inheritance_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; +} + +/* + * build_inheritance_child_rel + * Build a RelOptInfo for child relation of an inheritance set + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields based on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turn out to be self-contradictory, + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + List *childquals; + ListCell *lc; + bool have_const_false_cq; + Index cq_min_security; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = false; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, parent->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual = (Node *) rinfo->clause; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, childqual, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might + * have securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals.) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except + * that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* + * not likely that we'd see constants here, so no + * check + */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, + false, + false, + security_level, + NULL, NULL, + NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* Set child's version of baserestrictinfo. */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; } /* @@ -583,7 +926,7 @@ create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) * column, filling placeholder entries for dropped columns, etc., all of * which occurs with the child's TupleDesc. */ - tlist = preprocess_targetlist(subroot); + tlist = preprocess_targetlist(subroot, true); subroot->processed_tlist = tlist; /* Add any newly added Vars to the child RelOptInfo. */ diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 0a88c14483..8353e78fb8 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -105,20 +105,20 @@ static void set_baserel_partition_key_exprs(Relation relation, * important for it. */ void -get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) +get_relation_info(PlannerInfo *root, RangeTblEntry *rte, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; bool hasindex; List *indexinfos = NIL; + bool inhparent = rte->inh; /* * We need not lock the relation since it was already locked, either by * the rewriter or when expand_inherited_rtentry() added it to the query's * rangetable. */ - relation = table_open(relationObjectId, NoLock); + relation = heap_open(rte->relid, NoLock); /* Temporary and unlogged relations are inaccessible during recovery. */ if (!RelationNeedsWAL(relation) && RecoveryInProgress()) @@ -444,11 +444,32 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, get_relation_foreign_keys(root, rel, relation, inhparent); /* - * Collect info about relation's partitioning scheme, if any. Only - * inheritance parents may be partitioned. + * Collect some additional information for inheritance parents. */ - if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - set_relation_partition_info(root, rel, relation); + if (inhparent) + { + /* + * We'll need the TupleDesc when initializing the child relation. + * A copy is being made because concurrent changes might drop + * the relcache entry. That's possible because ALTER TABLE + * child_table NO INHERIT parent_table only requires an + * AccessShareLock on parent_table. + */ + rel->tupdesc = CreateTupleDescCopy(RelationGetDescr(relation)); + rel->reltype = RelationGetForm(relation)->reltype; + + /* + * If partitioned, also save the information of partitioning scheme, + * and whether the query updates any of the partition key columns. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + set_relation_partition_info(root, rel, relation); + root->partColsUpdated |= has_partition_attrs(relation, + rte->updatedCols, + NULL); + } + } table_close(relation, NoLock); @@ -458,7 +479,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * removing an index, or adding a hypothetical index to the indexlist. */ if (get_relation_info_hook) - (*get_relation_info_hook) (root, relationObjectId, inhparent, rel); + (*get_relation_info_hook) (root, rte->relid, rte->inh, rel); } /* @@ -1844,16 +1865,20 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; + + /* + * Since we must've taken a lock on the table, it's okay to simply copy + * the pointers to relcache data here. + */ + rel->part_oids = partdesc->oids; + rel->boundinfo = partdesc->boundinfo; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index fe83ec4519..d12d047002 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -132,6 +132,50 @@ setup_append_rel_array(PlannerInfo *root) } /* + * expand_planner_arrays + * Expand the PlannerInfo arrays by add_size members and initialize the + * the newly added bytes with zero + */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size; + + Assert(add_size > 0); + + new_size = root->simple_rel_array_size + add_size; + + /* Expand various arrays and 0-initialize added bytes. */ + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + + if (root->append_rel_array) + { + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + } + else + { + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + } + + root->simple_rel_array_size = new_size; +} + +/* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. */ @@ -195,6 +239,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->joininfo = NIL; rel->has_eclass_joins = false; rel->consider_partitionwise_join = false; /* might get changed later */ + rel->top_parent_relids = NULL; /* might be changed later */ rel->part_scheme = NULL; rel->nparts = 0; rel->boundinfo = NULL; @@ -203,6 +248,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->partexprs = NULL; rel->nullable_partexprs = NULL; rel->partitioned_child_rels = NIL; + rel->inh_root_parent = 0; /* might be changed later */ /* * Pass top parent's relids down the inheritance hierarchy. If the parent @@ -216,16 +262,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->top_parent_relids = parent->top_parent_relids; else rel->top_parent_relids = bms_copy(parent->relids); + + /* + * For inheritance child relations, we also set inh_root_parent. + * Note that 'parent' might itself be a child (a sub-partitioned + * partition), in which case we simply use its value of + * inh_root_parent. + */ + if (parent->rtekind == RTE_RELATION) + rel->inh_root_parent = parent->inh_root_parent > 0 ? + parent->inh_root_parent : + parent->relid; } - else - rel->top_parent_relids = NULL; /* Check type of rtable entry */ switch (rte->rtekind) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte, rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -266,52 +321,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); - /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. - */ - if (rte->inh) - { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); - - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; - } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); - } - return rel; } diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 901433c68c..9bb472fd98 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -46,6 +46,7 @@ #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" #include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/planner.h" #include "optimizer/predtest.h" @@ -437,17 +438,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -541,23 +548,20 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ @@ -565,6 +569,13 @@ prune_append_rel_partitions(RelOptInfo *rel) return NULL; /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. */ @@ -591,15 +602,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index beb2e323a8..01a664d246 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -15,6 +15,7 @@ #define RELATION_H #include "access/sdir.h" +#include "access/tupdesc.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/params.h" @@ -729,11 +730,28 @@ typedef struct RelOptInfo int nparts; /* number of partitions */ struct PartitionBoundInfoData *boundinfo; /* Partition bounds */ List *partition_qual; /* partition constraint */ + Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ + + /* + * For inheritance children, this is the RT index of inheritance table + * mentioned in the query from which this relation originated. + * top_parent_relids cannot be used for this, because if the inheritance + * root table is itself under UNION ALL, top_parent_relids contains the + * RT index of UNION ALL parent subquery. + */ + Index inh_root_parent; + + /* + * Set only if this is an inheritance parent relation. This information + * is needed when initializing the planning info for children. + */ + TupleDesc tupdesc; /* A "copy" of the table's tuple desriptor */ + Oid reltype; /* Table's reltype */ } RelOptInfo; /* diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h index 604e36d73c..8f7b06a608 100644 --- a/src/include/optimizer/appendinfo.h +++ b/src/include/optimizer/appendinfo.h @@ -17,9 +17,10 @@ #include "nodes/relation.h" #include "utils/relcache.h" -extern AppendRelInfo *make_append_rel_info(Relation parentrel, - Relation childrel, - Index parentRTindex, Index childRTindex); +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parent, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childtype, + Index childRTindex); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index bd905d3328..e7d0a20c54 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -261,6 +261,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index a1b23251a1..d8091cdfa4 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -25,8 +25,8 @@ typedef void (*get_relation_info_hook_type) (PlannerInfo *root, extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; -extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); +extern void get_relation_info(PlannerInfo *root, RangeTblEntry *rte, + RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 62d45dd142..a329e87fc2 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -38,7 +38,8 @@ extern Expr *canonicalize_qual(Expr *qual, bool is_check); /* * prototypes for preptlist.c */ -extern List *preprocess_targetlist(PlannerInfo *root); +extern List *preprocess_targetlist(PlannerInfo *root, + bool inheritance_expanded); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h index ce9bc8d9fd..5c5d27d040 100644 --- a/src/include/partitioning/partprune.h +++ b/src/include/partitioning/partprune.h @@ -76,7 +76,7 @@ extern PartitionPruneInfo *make_partition_pruneinfo(PlannerInfo *root, List *subpaths, List *partitioned_rels, List *prunequal); -extern Relids prune_append_rel_partitions(RelOptInfo *rel); +extern Bitmapset *prune_append_rel_partitions(RelOptInfo *rel); extern Bitmapset *get_matching_partitions(PartitionPruneContext *context, List *pruning_steps); diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 6bc106831e..1450cef057 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) -- 2.11.0
v17-0003-Teach-planner-to-only-process-unpruned-partition.patch (text/plain, 6.8K) download From dc0518738007be1a6d0f543e0df1df209a79e45c Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 15 Oct 2018 10:59:24 +0900 Subject: [PATCH v17 3/4] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/inherit.c | 1 + src/backend/optimizer/util/relnode.c | 3 +++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/relation.h | 4 ++++ 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index bc89de9a9d..3d5b2df8d8 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1451,6 +1451,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 738ab0a37d..a3165785a9 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6833,7 +6833,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -6841,9 +6843,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -6924,7 +6924,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -6936,7 +6935,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -6946,9 +6947,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 71d9113ab2..503c543ebf 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -361,6 +361,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, /* Perform pruning. */ partindexes = prune_append_rel_partitions(parentrel); + parentrel->live_parts = partindexes; /* Must expand PlannerInfo arrays before we can add children. */ if (bms_num_members(partindexes) > 0) diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index d12d047002..059ab8f35e 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1757,6 +1757,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 9bb472fd98..088b191bdd 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -441,29 +441,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 01a664d246..d27611c2c7 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -733,6 +733,10 @@ typedef struct RelOptInfo Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Indexes into part_rels of the non-NULL + * RelOptInfos of unpruned partitions; exists + * to avoid having to iterate over the entire + * part_rels array to filter NULL entries. */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.11.0
v17-0004-Do-not-lock-all-partitions-at-the-beginning.patch (text/plain, 1.9K) download From 813e7be5741384e850d1d7fceb19084e4bdc79e6 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 12 Oct 2018 19:12:53 +0900 Subject: [PATCH v17 4/4] Do not lock all partitions at the beginning --- src/backend/optimizer/util/inherit.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 503c543ebf..bbd76c925e 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -328,10 +328,6 @@ expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, * * Partitions are added to the query in order in which they are found in * the parent's PartitionDesc. - * - * Note: even though only the unpruned partitions will be added to the - * resulting plan, this still locks *all* partitions via find_all_inheritors - * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, @@ -345,10 +341,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, parentrel->inh_root_parent : parentRTindex; - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - /* * Initialize partitioned_child_rels to contain this RT index. * @@ -385,8 +377,11 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, RangeTblEntry *childrte; Index childRTindex; - /* Open rel; we already have required locks */ - newrelation = table_open(childOID, NoLock); + /* + * Open rel; this's the first time of opening partitions for this + * query, so take the appropriate locks. + */ + newrelation = table_open(childOID, lockmode); Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-01-24 06:09  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 2 replies; 265+ messages in thread

From: Imai, Yoshikazu @ 2019-01-24 06:09 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Rowley <david.rowley@2ndquadrant.com>; Amit Langote <amitlangote09@gmail.com>; pgsql-hackers

On Wed, Jan 23, 2019 at 1:35 AM, Amit Langote wrote:
> Rebased due to the heap_open/close() -> table_open/close() change.

Maybe there are not many things I can point out through reviewing the patch, so I ran the performance test against v17 patches instead of reviewing codes.
There are already a lot of tests about partition pruning case and we confirmed performance improves in those cases. In this time, I tested about accessing all partitions case.

I tested with master, master + 0001, master + 0001 + 0002, ..., master + 0001 + 0002 + 0003 + 0004.
I ran pgbench 3 times in each test case and below results are average of those.

[postgresql.conf]
max_parallel_workers = 0
max_parallel_workers_per_gather = 0

[partition table definitions(8192 partitions case)]
create table rt (a int, b int, c int) partition by range (a)
create table rt_1 partition of rt for values from (1) to (2);
...
create table rt_8192 partition of rt for values from (8191) to (8192);

[pgbench commands]
pgbench -n -f update.sql -T 30 postgres

[update.sql(updating partkey case)]
update rt set a = 1;

[update.sql(updating non-partkey case)]
update rt set b = 1;

[results]
updating partkey case:

part-num  master     0001     0002     0003     0004
1        8215.34  7924.99  7931.15  8407.40  8475.65 
2        7137.49  7026.45  7128.84  7583.08  7593.73 
4        5880.54  5896.47  6014.82  6405.33  6398.71 
8        4222.96  4446.40  4518.54  4802.43  4785.82 
16       2634.91  2891.51  2946.99  3085.81  3087.91 
32        935.12  1125.28  1169.17  1199.44  1202.04 
64        352.37   405.27   417.09   425.78   424.53 
128       236.26   310.01   307.70   315.29   312.81 
256        65.36    86.84    87.67    84.39    89.27 
512        18.34    24.84    23.55    23.91    23.91 
1024        4.83     6.93     6.51     6.45     6.49 


updating non-partkey case:

part-num   master    0001     0002     0003      0004
1        8862.58  8421.49  8575.35  9843.71  10065.30   
2        7715.05  7575.78  7654.28  8800.84   8720.60   
4        6249.95  6321.32  6470.26  7278.14   7280.10   
8        4514.82  4730.48  4823.37  5382.93   5341.10   
16       2815.21  3123.27  3162.51  3422.36   3393.94   
32        968.45  1702.47  1722.38  1809.89   1799.88   
64        364.17   420.48   432.87   440.20    435.31   
128       119.94   148.77   150.47   152.18    143.35   
256        45.09    46.35    46.93    48.30     45.85   
512         8.74    10.59    10.23    10.27     10.13   
1024        2.28     2.60     2.56     2.57      2.51   


Looking at the results, if we only apply 0001 or 0001 + 0002 and if number of partition is few like 1 or 2, performance degrades compare to master(A maximum reduction is about 5%, which is 8863->8421).
In all other cases, performance improves compare to master.

--
Yoshikazu Imai



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-01-28 01:44  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  1 sibling, 1 reply; 265+ messages in thread

From: Imai, Yoshikazu @ 2019-01-28 01:44 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Rowley <david.rowley@2ndquadrant.com>; Amit Langote <amitlangote09@gmail.com>; pgsql-hackers

On Thu, Jan 24, 2019 at 6:10 AM, Imai, Yoshikazu wrote:
> updating partkey case:
> 
> part-num  master     0001     0002     0003     0004
> 1        8215.34  7924.99  7931.15  8407.40  8475.65
> 2        7137.49  7026.45  7128.84  7583.08  7593.73
> 4        5880.54  5896.47  6014.82  6405.33  6398.71
> 8        4222.96  4446.40  4518.54  4802.43  4785.82
> 16       2634.91  2891.51  2946.99  3085.81  3087.91
> 32        935.12  1125.28  1169.17  1199.44  1202.04
> 64        352.37   405.27   417.09   425.78   424.53
> 128       236.26   310.01   307.70   315.29   312.81
> 256        65.36    86.84    87.67    84.39    89.27
> 512        18.34    24.84    23.55    23.91    23.91
> 1024        4.83     6.93     6.51     6.45     6.49

I also tested with non-partitioned table case.

updating partkey case:

part-num  master     0001     0002     0003     0004
0        10956.7  10370.5  10472.6  10571.0  10581.5
1        8215.34  7924.99  7931.15  8407.40  8475.65 
...
1024        4.83     6.93     6.51     6.45     6.49


In my performance results, it seems update performance degrades in non-partitioned case with v17-patch applied.
But it seems this degrades did not happen at v2-patch.

On Thu, Aug 30, 2018 at 1:45 AM, Amit, Langote wrote:
> UPDATE:
> 
> nparts  master    0001    0002   0003
> ======  ======    ====    ====   ====
> 0         2856    2893    2862   2816

Does this degradation only occur in my tests? Or if this result is correct, what may causes the degradation?

--
Yoshikazu Imai



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-28 04:18  David Rowley <david.rowley@2ndquadrant.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 1 reply; 265+ messages in thread

From: David Rowley @ 2019-01-28 04:18 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On Sat, 12 Jan 2019 at 02:00, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
>
> On 2019/01/09 9:09, David Rowley wrote:
> > postgres=# update parent set c = c where a = 333;
> > server closed the connection unexpectedly
> >         This probably means the server terminated abnormally
> >         before or while processing the request.
> >
> > I didn't look into what's causing the crash.
>
> I tried your example, but it didn't crash for me:
>
> explain update parent set c = c where a = 333;
>                      QUERY PLAN
> ────────────────────────────────────────────────────
>  Update on parent  (cost=0.00..0.00 rows=0 width=0)
>    ->  Result  (cost=0.00..0.00 rows=0 width=54)
>          One-Time Filter: false
> (3 rows)

I had a closer look. The crash is due to
set_inherit_target_rel_sizes() forgetting to set has_live_children to
false. This results in the relation not properly being set to a dummy
rel and the code then making a modify table node without any subnodes.
That crashes due to getTargetResultRelInfo() returning NULL due to
rootResultRelInfo and resultRelInfo both being NULL.

The attached fixes it. If you were not seeing the crash then
has_live_children must have been zero/false by chance during your
test.

A simple case of:

create table listp (a int, b int) partition by list(a);
create table listp1 partition of listp for values in(1);
update listp set b = b + 1 where a = 42;

was crashing for me.

-- 
 David Rowley                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

v17_fixup.diff (application/octet-stream, 527B) download diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index ab4f376e92..11ddc857bc 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -956,7 +956,7 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { int parentRTindex = rti; - bool has_live_children; + bool has_live_children = false; ListCell *l; /* Guard against stack overflow due to overly deep inheritance tree. */
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-28 05:59  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  1 sibling, 0 replies; 265+ messages in thread

From: Amit Langote @ 2019-01-28 05:59 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Rowley <david.rowley@2ndquadrant.com>; Amit Langote <amitlangote09@gmail.com>; pgsql-hackers

Imai-san,

Thanks for testing.

On 2019/01/24 15:09, Imai, Yoshikazu wrote:
> [pgbench commands]
> pgbench -n -f update.sql -T 30 postgres
> 
> [update.sql(updating partkey case)]
> update rt set a = 1;
> 
> [update.sql(updating non-partkey case)]
> update rt set b = 1;
> 
> [results]
> updating partkey case:
> 
> part-num  master     0001     0002     0003     0004
> 1        8215.34  7924.99  7931.15  8407.40  8475.65 
> 2        7137.49  7026.45  7128.84  7583.08  7593.73 
> 4        5880.54  5896.47  6014.82  6405.33  6398.71 
> 8        4222.96  4446.40  4518.54  4802.43  4785.82 
> 16       2634.91  2891.51  2946.99  3085.81  3087.91 
> 32        935.12  1125.28  1169.17  1199.44  1202.04 
> 64        352.37   405.27   417.09   425.78   424.53 
> 128       236.26   310.01   307.70   315.29   312.81 
> 256        65.36    86.84    87.67    84.39    89.27 
> 512        18.34    24.84    23.55    23.91    23.91 
> 1024        4.83     6.93     6.51     6.45     6.49 
> 
> 
> updating non-partkey case:
> 
> part-num   master    0001     0002     0003      0004
> 1        8862.58  8421.49  8575.35  9843.71  10065.30   
> 2        7715.05  7575.78  7654.28  8800.84   8720.60   
> 4        6249.95  6321.32  6470.26  7278.14   7280.10   
> 8        4514.82  4730.48  4823.37  5382.93   5341.10   
> 16       2815.21  3123.27  3162.51  3422.36   3393.94   
> 32        968.45  1702.47  1722.38  1809.89   1799.88   
> 64        364.17   420.48   432.87   440.20    435.31   
> 128       119.94   148.77   150.47   152.18    143.35   
> 256        45.09    46.35    46.93    48.30     45.85   
> 512         8.74    10.59    10.23    10.27     10.13   
> 1024        2.28     2.60     2.56     2.57      2.51   
> 
> 
> Looking at the results, if we only apply 0001 or 0001 + 0002 and if number of partition is few like 1 or 2, performance degrades compare to master(A maximum reduction is about 5%, which is 8863->8421).
> In all other cases, performance improves compare to master.

Just to be clear, these are cases where pruning *doesn't* occur, though
we'll still need to at least figure out why the degradation occurs for
small number of partitions.

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-28 08:21  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: David Rowley <david.rowley@2ndquadrant.com>
  0 siblings, 1 reply; 265+ messages in thread

From: Amit Langote @ 2019-01-28 08:21 UTC (permalink / raw)
  To: David Rowley <david.rowley@2ndquadrant.com>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Hi David,

On 2019/01/28 13:18, David Rowley wrote:
> On Sat, 12 Jan 2019 at 02:00, Amit Langote wrote:
>> On 2019/01/09 9:09, David Rowley wrote:
>>> postgres=# update parent set c = c where a = 333;
>>> server closed the connection unexpectedly
>>>         This probably means the server terminated abnormally
>>>         before or while processing the request.
>>>
>>> I didn't look into what's causing the crash.
>>
>> I tried your example, but it didn't crash for me:
>>
>> explain update parent set c = c where a = 333;
>>                      QUERY PLAN
>> ────────────────────────────────────────────────────
>>  Update on parent  (cost=0.00..0.00 rows=0 width=0)
>>    ->  Result  (cost=0.00..0.00 rows=0 width=54)
>>          One-Time Filter: false
>> (3 rows)
> 
> I had a closer look. The crash is due to
> set_inherit_target_rel_sizes() forgetting to set has_live_children to
> false. This results in the relation not properly being set to a dummy
> rel and the code then making a modify table node without any subnodes.
> That crashes due to getTargetResultRelInfo() returning NULL due to
> rootResultRelInfo and resultRelInfo both being NULL.

Oops, you're right.

> The attached fixes it. If you were not seeing the crash then
> has_live_children must have been zero/false by chance during your
> test.

Thanks for the fix, I'll incorporate it in the next version I'll post by
tomorrow.

Regards,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-29 02:23  David Rowley <david.rowley@2ndquadrant.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 1 reply; 265+ messages in thread

From: David Rowley @ 2019-01-29 02:23 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On Mon, 28 Jan 2019 at 21:21, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> Thanks for the fix, I'll incorporate it in the next version I'll post by
> tomorrow.

I just started reading 0001 again. I made a few notes:

1. Should bms_del_members() instead of bms_difference() if you don't
mind modifying in place, which it sounds like you don't, going by the
comment.

/*
* Now fix up EC's relids set.  It's OK to modify EC like this,
* because caller must have made a copy of the original EC.
* For example, see adjust_inherited_target_child_root.
*/
cur_ec->ec_relids = bms_difference(cur_ec->ec_relids,
   parent_rel->relids);
cur_ec->ec_relids = bms_add_members(cur_ec->ec_relids,
child_rel->relids);

2.  In the comment:

/*
* Now fix up EC's relids set.  It's OK to modify EC like this,
* because caller must have made a copy of the original EC.
* For example, see adjust_inherited_target_child_root.
*/

You mention that the caller must have made a copy, but the header
comment mentions nothing about that to warn callers.

3. Would it be better to do the following in make_rel_from_joinlist()?

/*
* Check that we got at least one usable path.  In the case of an
* inherited update/delete operation, no path has been created for
* the query's actual target relation yet.
*/
if (!root->inherited_update &&
(!final_rel ||
!final_rel->cheapest_total_path ||
final_rel->cheapest_total_path->param_info != NULL))
elog(ERROR, "failed to construct the join relation");

That way the test is also performed for each partition's join problem
and you don't need that weird special case to disable the check.

4. Do you think it would be nicer if
inheritance_make_rel_from_joinlist returned rel?

inheritance_make_rel_from_joinlist(root, joinlist);

/*
* Return the RelOptInfo of original target relation, although this
* doesn't really contain the final path.  inheritance_planner
* from where we got here will generate the final path, but it will
* do so by iterative over child subroots, not through this
* RelOptInfo.
*/
rel = find_base_rel(root, root->parse->resultRelation);

5. "iterative over child subroots" -> "iterating over the child subroots".

6. In set_inherit_target_rel_sizes() the
!bms_is_member(appinfo->child_relid, live_children) check could be
moved much earlier, likely just after the if (appinfo->parent_relid !=
parentRTindex) check.

I understand you're copying set_append_rel_size() here, but I don't
quite understand the reason why that function is doing it that way.
Seems like wasted effort building the quals for pruned partitions.

7. In set_inherit_target_rel_sizes() I still don't really like the way
you're adjusting the EquivalenceClasses. Would it not be better to
invent a function similar to adjust_appendrel_attrs(), or just use
that function?

8. Still a typo in this comment:

+ * ass dummy.  We must do this in this phase so that the rel's

9. "parent's" -> "the parent's"

/* Also propagate this child's own children into parent's list. */

10. Too many "of":

 * For each child of of the query's result relation, this translates the

11. Badly formed multiline comment.

/* Save the just translated targetlist as unexpanded_tlist in the child's
* subroot, so that this child's own children can use it.  Must use copy

12. DEELETE -> DELETE

/*
* The following fields are set during query planning portion of an
* inherited UPDATE/DEELETE operation.
*/

13. Surely this is not true?

* non-NULL.  Content of each PlannerInfo is same as the parent
* PlannerInfo, except for the parse tree which is a translated copy of
* the parent's parse tree.

Are you not modifying the EquivalenceClasses?

14. This comment plus the variable name is confusing me:

/*
* RelOptInfos corresponding to each child target rel.  For leaf children,
* it's the RelOptInfo representing the output of make_rel_from_joinlist()
* called with the parent rel in the original join tree replaced by a
* given leaf child.  For non-leaf children, it's the baserel RelOptInfo
* itself, left as a placeholder.
*/
List    *inh_target_child_joinrels;

The variable name mentions joinrels, but the comment target rels.  A
join rel can't be the target of an UPDATE/DELETE.

-- 
 David Rowley                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-29 09:32  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: David Rowley <david.rowley@2ndquadrant.com>
  0 siblings, 3 replies; 265+ messages in thread

From: Amit Langote @ 2019-01-29 09:32 UTC (permalink / raw)
  To: David Rowley <david.rowley@2ndquadrant.com>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On 2019/01/29 11:23, David Rowley wrote:
> On Mon, 28 Jan 2019 at 21:21, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> Thanks for the fix, I'll incorporate it in the next version I'll post by
>> tomorrow.
> 
> I just started reading 0001 again. I made a few notes:

Thanks.

> 1. Should bms_del_members() instead of bms_difference() if you don't
> mind modifying in place, which it sounds like you don't, going by the
> comment.
> 
> /*
> * Now fix up EC's relids set.  It's OK to modify EC like this,
> * because caller must have made a copy of the original EC.
> * For example, see adjust_inherited_target_child_root.
> */
> cur_ec->ec_relids = bms_difference(cur_ec->ec_relids,
>    parent_rel->relids);
> cur_ec->ec_relids = bms_add_members(cur_ec->ec_relids,
> child_rel->relids);

Fixed.

> 2.  In the comment:
> 
> /*
> * Now fix up EC's relids set.  It's OK to modify EC like this,
> * because caller must have made a copy of the original EC.
> * For example, see adjust_inherited_target_child_root.
> */
> 
> You mention that the caller must have made a copy, but the header
> comment mentions nothing about that to warn callers.

OK, added a sentence.

> 3. Would it be better to do the following in make_rel_from_joinlist()?
> 
> /*
> * Check that we got at least one usable path.  In the case of an
> * inherited update/delete operation, no path has been created for
> * the query's actual target relation yet.
> */
> if (!root->inherited_update &&
> (!final_rel ||
> !final_rel->cheapest_total_path ||
> final_rel->cheapest_total_path->param_info != NULL))
> elog(ERROR, "failed to construct the join relation");
> 
> That way the test is also performed for each partition's join problem
> and you don't need that weird special case to disable the check.

Moving it inside make_rel_from_joinlist() seems a bit awkward as there are
many return statements, with those appearing earlier in the function being
being for fast-path cases.

How about in the direct callers of make_rel_from_joinlist() viz.
make_one_rel() and inheritance_make_rel_from_joinlist()?

> 4. Do you think it would be nicer if
> inheritance_make_rel_from_joinlist returned rel?
> 
> inheritance_make_rel_from_joinlist(root, joinlist);
> 
> /*
> * Return the RelOptInfo of original target relation, although this
> * doesn't really contain the final path.  inheritance_planner
> * from where we got here will generate the final path, but it will
> * do so by iterative over child subroots, not through this
> * RelOptInfo.
> */
> rel = find_base_rel(root, root->parse->resultRelation);

OK, done.  It now returns RelOptInfo root->parse->resultRelation.

> 5. "iterative over child subroots" -> "iterating over the child subroots".

Sentence no longer exists after above adjustments.

> 6. In set_inherit_target_rel_sizes() the
> !bms_is_member(appinfo->child_relid, live_children) check could be
> moved much earlier, likely just after the if (appinfo->parent_relid !=
> parentRTindex) check.
> 
> I understand you're copying set_append_rel_size() here, but I don't
> quite understand the reason why that function is doing it that way.
> Seems like wasted effort building the quals for pruned partitions.

Yeah, fixed that in both functions.  Actually, as you might be aware, the
next patch obviates the need for this stanza at all, because pruned
partitions aren't added to root->append_rel_list.

> 7. In set_inherit_target_rel_sizes() I still don't really like the way
> you're adjusting the EquivalenceClasses. Would it not be better to
> invent a function similar to adjust_appendrel_attrs(), or just use
> that function?

OK, I added a function copy_eq_classes_for_child_root() that simply makes
a copy of eq_classes from the source root (deep-copying where applicable)
and returns the list.

> 8. Still a typo in this comment:
> 
> + * ass dummy.  We must do this in this phase so that the rel's

Sorry, fixed.

> 9. "parent's" -> "the parent's"
> 
> /* Also propagate this child's own children into parent's list. */
> 
> 10. Too many "of":
> 
>  * For each child of of the query's result relation, this translates the
> 
> 11. Badly formed multiline comment.
> 
> /* Save the just translated targetlist as unexpanded_tlist in the child's
> * subroot, so that this child's own children can use it.  Must use copy
> 
> 12. DEELETE -> DELETE
> 
> /*
> * The following fields are set during query planning portion of an
> * inherited UPDATE/DEELETE operation.
> */
> 
> 13. Surely this is not true?
> 
> * non-NULL.  Content of each PlannerInfo is same as the parent
> * PlannerInfo, except for the parse tree which is a translated copy of
> * the parent's parse tree.
> 
> Are you not modifying the EquivalenceClasses?

Yep, fixed.  Also fixed items 9-12.

> 14. This comment plus the variable name is confusing me:
> 
> /*
> * RelOptInfos corresponding to each child target rel.  For leaf children,
> * it's the RelOptInfo representing the output of make_rel_from_joinlist()
> * called with the parent rel in the original join tree replaced by a
> * given leaf child.  For non-leaf children, it's the baserel RelOptInfo
> * itself, left as a placeholder.
> */
> List    *inh_target_child_joinrels;
> 
> The variable name mentions joinrels, but the comment target rels.  A
> join rel can't be the target of an UPDATE/DELETE.

How about inh_target_child_path_rels?  The final ModifyTable's child
"subpaths" have to hang off of some RelOptInfo until we've done the final
grouping_planner() steps.  Those RelOptInfos are stored here.


Attached updated patches.

Thanks,
Amit

PS: replies might be slow until Feb 5 (attending FOSDEM for the first time!)

Attachments: download all (4 files, .tar.gz)

v18-0001-Overhaul-inheritance-update-delete-planning.patch (text/plain, 77.3K) download From 238a562a1dd5de97a7f42d18e20642ec134085fc Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 11:18:12 +0900 Subject: [PATCH v18 1/4] Overhaul inheritance update/delete planning With the current method, inheritance_planner, which handles the planning for update/delete commands targeting inheritance trees, repeatedly calls query_planner for each child table. That's inefficient for two reasons: 1. partprune.c cannot be used to perform partition pruning, because it can only be invoked from query_planner. With the current approach, query_planner only sees the individual partitions in the query tree it receives, not the parent partitioned table. This leaves each partition to be pruned using constraint exclusion. (constraint exclusion cannon prune hash partitions which means UPDATE/DELETE don't support hash partition pruning.) 2. Repeated invocation of query_planner results in allocating large amounts of memory, especially if there are many child tables. Also, it involves repeatedly performing the same processing, such as jointree processing. This commit addresses both of the issues by modifying query_planner to handle the inheritance target case directly. Instead of having the caller create separate PlannerInfo's for individual child target relations and pass them to query_planner (actually, via grouping_planner), this teaches query_planner itself to create the child PlannerInfo's by the original parent PlannerInfo. It creates them *after* it has generated enough state in the latter, such as RelOptInfos for various relations, EquivalenceClasses after processing the join tree, and *before* generating the scan and join paths. Scan paths are generated normally, except that Paths of target children are not "appended". Join paths are generated separately for each child target relation, that is, by replacing the parent relation references in the original join tree with the child relation references. Joinrels (RelOptInfos thereof) corresponding to various child target relation are saved in the parent PlannerInfo for further processing. inheritance_planner now calls query_planner just once at the beginning with the original unmodified query, which takes care of generating top-level join rels for the child queries as described above. For each child join rel, it then calls grouping_planner using previously created child PlannerInfo to finish up the paths such that they produce query's top-level target list expanded according to a given child relation's descriptor. grouping_planner()'s interface is modified so that we can pass it what's called a 'planned_rel', a RelOptInfo that already contains the necessary paths needed to produce its output. This removes some existing code in inheritance_planner that dealt with any subquery RTEs in the query. The rationale of that code was that the subquery RTEs may change during each iteration of planning (that is, for different children), so different iterations better use different copies of those RTEs. That was handled by making fresh copies of those RTEs for each iteration of planning which were appended to the range table, accompanied by modifying all expressions and auxiliary structures that referenced the original subquery RTEs to instead reference the copies (that is, change the varnos). This copying meant we would end up adding S * N new entries to the original range table by the time we got to the last unpruned child, where S is the number of subquery RTEs in the original query and N the number of unpruned children. Since with the new code we perform planning just once, I think we don't need this special handling. Actually, there is a regression test output change due to no longer having copies of subquery RTEs (see the diff of partition_join.out file.) Also we no longer need the hack in relation_excluded_by_constraints() involving setting PlannerInfo.inhTargetKind to enable constraint exclusion for target child relation, which is no longer needed. Constraint exclusion now runs during query_planner step described above. Regression test output change in partition_join.out is due to the fact that we no longer create duplicates of subquery RTEs in child range tables. --- doc/src/sgml/ddl.sgml | 17 +- src/backend/nodes/outfuncs.c | 2 +- src/backend/optimizer/path/allpaths.c | 620 +++++++++++++++++++++++++-- src/backend/optimizer/path/equivclass.c | 59 ++- src/backend/optimizer/path/joinrels.c | 2 +- src/backend/optimizer/plan/createplan.c | 10 - src/backend/optimizer/plan/planmain.c | 41 +- src/backend/optimizer/plan/planner.c | 444 ++++++------------- src/backend/optimizer/prep/prepjointree.c | 1 - src/backend/optimizer/util/appendinfo.c | 33 +- src/backend/optimizer/util/inherit.c | 150 +++++++ src/backend/optimizer/util/plancat.c | 73 +--- src/include/nodes/relation.h | 38 +- src/include/optimizer/inherit.h | 2 + src/include/optimizer/paths.h | 3 +- src/test/regress/expected/partition_join.out | 4 +- 16 files changed, 1024 insertions(+), 475 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 85e4358988..1bb5ca6b08 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -4439,24 +4439,11 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <note> <para> - Currently, pruning of partitions during the planning of an - <command>UPDATE</command> or <command>DELETE</command> command is - implemented using the constraint exclusion method (however, it is - controlled by the <literal>enable_partition_pruning</literal> rather than - <literal>constraint_exclusion</literal>) &mdash; see the following section - for details and caveats that apply. - </para> - - <para> Execution-time partition pruning currently only occurs for the <literal>Append</literal> and <literal>MergeAppend</literal> node types. It is not yet implemented for the <literal>ModifyTable</literal> node - type. - </para> - - <para> - Both of these behaviors are likely to be changed in a future release - of <productname>PostgreSQL</productname>. + type, but that is likely to be changed in a future release of + <productname>PostgreSQL</productname>. </para> </note> </sect2> diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 33f7939e05..d1e0862c69 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2210,7 +2210,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); WRITE_UINT_FIELD(qual_security_level); - WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind); WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasHavingQual); @@ -2220,6 +2219,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_BITMAPSET_FIELD(curOuterRels); WRITE_NODE_FIELD(curOuterParams); WRITE_BOOL_FIELD(partColsUpdated); + WRITE_BOOL_FIELD(inherited_update); } static void diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 0d8a3f9592..435b26e097 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -38,6 +38,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" @@ -92,8 +93,12 @@ static void set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); +static void set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte); static void set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); +static void set_inherit_target_rel_pathlists(PlannerInfo *root, + RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, @@ -122,6 +127,8 @@ static void set_result_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); +static RelOptInfo *inheritance_make_rel_from_joinlist(PlannerInfo *root, + List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, pushdown_safety_info *safetyInfo); static bool recurse_pushdown_safe(Node *setOp, Query *topquery, @@ -152,27 +159,6 @@ make_one_rel(PlannerInfo *root, List *joinlist) Index rti; double total_pages; - /* - * Construct the all_baserels Relids set. - */ - root->all_baserels = NULL; - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - - /* there may be empty slots corresponding to non-baserel RTEs */ - if (brel == NULL) - continue; - - Assert(brel->relid == rti); /* sanity check on array */ - - /* ignore RTEs that are "other rels" */ - if (brel->reloptkind != RELOPT_BASEREL) - continue; - - root->all_baserels = bms_add_member(root->all_baserels, brel->relid); - } - /* Mark base rels as to whether we care about fast-start plans */ set_base_rel_consider_startup(root); @@ -220,13 +206,35 @@ make_one_rel(PlannerInfo *root, List *joinlist) /* * Generate access paths for the entire join tree. + * + * For UPDATE/DELETE on an inheritance parent, join paths should be + * generated for each child result rel separately. */ - rel = make_rel_from_joinlist(root, joinlist); + if (root->parse->resultRelation && + root->simple_rte_array[root->parse->resultRelation]->inh) + { + /* + * RelOptInfo corresponding to the query's original target relation + * is returned. Join paths (if any) are attached to child joinrels, + * not this rel. Also, this rel's sole path (ModifyTable) will be set + * by inheritance_planner later, so we can't check its paths yet. + */ + rel = inheritance_make_rel_from_joinlist(root, joinlist); + } + else + { + rel = make_rel_from_joinlist(root, joinlist); - /* - * The result should join all and only the query's base rels. - */ - Assert(bms_equal(rel->relids, root->all_baserels)); + /* + * The result should join all and only the query's base rels. + */ + Assert(bms_equal(rel->relids, root->all_baserels)); + + /* Check that we got at least one usable path */ + if (!rel || !rel->cheapest_total_path || + rel->cheapest_total_path->param_info != NULL) + elog(ERROR, "failed to construct the join relation"); + } return rel; } @@ -310,9 +318,10 @@ set_base_rel_sizes(PlannerInfo *root) * If parallelism is allowable for this query in general, see whether * it's allowable for this rel in particular. We have to do this * before set_rel_size(), because (a) if this rel is an inheritance - * parent, set_append_rel_size() will use and perhaps change the rel's - * consider_parallel flag, and (b) for some RTE types, set_rel_size() - * goes ahead and makes paths immediately. + * parent, set_append_rel_size() or set_inherit_target_rel_sizes() + * will use and perhaps change the rel's consider_parallel flag, and + * (b) for some RTE types, set_rel_size() goes ahead and makes paths + * immediately. */ if (root->glob->parallelModeOK) set_rel_consider_parallel(root, rel, rte); @@ -376,8 +385,15 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_size(root, rel, rti, rte); + /* + * If it's a target relation, set the sizes of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (rti == root->parse->resultRelation) + set_inherit_target_rel_sizes(root, rel, rti, rte); + else + set_append_rel_size(root, rel, rti, rte); } else { @@ -466,14 +482,26 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { + bool inherited_update = false; + if (IS_DUMMY_REL(rel)) { /* We already proved the relation empty, so nothing more to do */ } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_pathlist(root, rel, rti, rte); + /* + * If it's a target relation, set the pathlists of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (root->inherited_update && root->parse->resultRelation == rti) + { + inherited_update = true; + set_inherit_target_rel_pathlists(root, rel, rti, rte); + } + else + set_append_rel_pathlist(root, rel, rti, rte); } else { @@ -551,8 +579,12 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, if (set_rel_pathlist_hook) (*set_rel_pathlist_hook) (root, rel, rti, rte); - /* Now find the cheapest of the paths for this rel */ - set_cheapest(rel); + /* + * Now find the cheapest of the paths for this rel, unless it's an + * inheritance parent and this is an update/delete operation. + */ + if (!inherited_update) + set_cheapest(rel); #ifdef OPTIMIZER_DEBUG debug_print_rel(root, rel); @@ -918,6 +950,356 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) } /* + * set_inherit_target_rel_sizes + * Set size estimates for the child target relations + * + * The passed-in rel represents the target relation of the query that is + * known to have inheritance children. This is very much like + * set_append_rel_size, except it doesn't set the size estimates for the + * passed-in rel itself, because we don't need to "append" the children + * in this case. + */ +static void +set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + bool has_live_children = false; + ListCell *l; + Relids live_children = NULL; + bool did_pruning = false; + + /* Guard against stack overflow due to overly deep inheritance tree. */ + check_stack_depth(); + + Assert(IS_SIMPLE_REL(rel)); + + /* + * If the partitioned relation has any baserestrictinfo quals then we + * attempt to use these quals to prune away partitions that cannot + * possibly contain any tuples matching these quals. In this case we'll + * store the relids of all partitions which could possibly contain a + * matching tuple, and skip anything else in the loop below. + */ + if (enable_partition_pruning && + rte->relkind == RELKIND_PARTITIONED_TABLE && + rel->baserestrictinfo != NIL) + { + live_children = prune_append_rel_partitions(rel); + did_pruning = true; + } + + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + List *childquals; + Index cq_min_security; + bool have_const_false_cq; + PlannerInfo *subroot; + ListCell *lc; + List *translated_exprs, + *child_target_exprs; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + + /* + * The child rel's RelOptInfo was already created during + * add_base_rels_to_query. + */ + childrel = find_base_rel(root, childRTindex); + Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); + + if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) + { + /* This partition was pruned; skip it. */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = NIL; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, + (Node *) rinfo->clause, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + /* track minimum security level among child quals */ + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might have + * securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals, see expand_inherited_rtentry().) Pull any such + * securityQuals up into the baserestrictinfo for the child. This is + * similar to process_security_barrier_quals() for the parent rel, + * except that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* not likely that we'd see constants here, so no check */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, false, false, + security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* + * OK, we've got all the baserestrictinfo quals for this child. + */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + if (relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* This child is excluded; skip it. */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Add missing Vars to child's reltarget. + * + * add_inherit_target_child_root() would've added only those that are + * needed to be present in the top-level tlist (or ones that + * preprocess_targetlist thinks are needed to be in the tlist.) We + * may need other attributes such as those contained in WHERE clauses, + * which are already computed for the parent during + * deconstruct_jointree processing of the original query (child's + * query never goes through deconstruct_jointree.) + */ + translated_exprs = (List *) + adjust_appendrel_attrs(root, + (Node *) rel->reltarget->exprs, + 1, &appinfo); + child_target_exprs = childrel->reltarget->exprs; + foreach(lc, translated_exprs) + { + Expr *expr = lfirst(lc); + + if (!list_member(child_target_exprs, expr)) + child_target_exprs = lappend(child_target_exprs, expr); + } + + subroot = root->inh_target_child_roots[childRTindex]; + + /* + * Also, We have to find any ECs containing parent's expressions and + * *replace* them with their copies containing child expressions. + */ + if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) + { + /* + * First make copies for ECs for the child's root, because some + * might be modified to replace parent EC expressions by child + * expressions in add_child_rel_equivalences. + */ + subroot->eq_classes = copy_eq_classes_for_child_root(root); + add_child_rel_equivalences(subroot, appinfo, rel, childrel, true); + } + childrel->has_eclass_joins = rel->has_eclass_joins; + + /* Translate join quals. */ + childrel->joininfo = (List *) + adjust_appendrel_attrs(subroot, + (Node *) rel->joininfo, + 1, &appinfo); + + /* + * If parallelism is allowable for this query in general, see whether + * it's allowable for this childrel in particular. For consistency, + * do this before calling set_rel_size() for the child. + */ + if (root->glob->parallelModeOK) + set_rel_consider_parallel(subroot, childrel, childRTE); + + /* + * Compute the child's size using possibly modified subroot. + */ + set_rel_size(subroot, childrel, childRTindex, childRTE); + + /* If the child itself is partitioned it may turn into a dummy rel. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* We have at least one live child. */ + has_live_children = true; + + Assert(childrel->rows > 0); + } + + if (has_live_children) + { + /* + * Set a non-zero value here to cope with the caller's requirement + * that non-dummy relations are actually not empty. We don't try to + * be accurate here, because we're not going to create a path that + * combines the children outputs. + */ + rel->rows = 1; + } + else + { + /* + * All children were excluded by constraints, so mark the relation + * as dummy. We must do this in this phase so that the rel's + * dummy-ness is visible when we generate paths for other rels. + */ + set_dummy_rel_pathlist(rel); + } +} + +/* + * set_inherit_target_rel_pathlists + * Build access paths for the child target relations + * + * Similar to set_append_rel_pathlist, except that we build paths of the + * children, but don't build an Append path. + */ +static void +set_inherit_target_rel_pathlists(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + ListCell *l; + + /* Nothing to do if all the children were excluded. */ + if (IS_DUMMY_REL(rel)) + return; + + /* Generate access paths for each of the children of passed-in rel */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + PlannerInfo *subroot; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + /* Re-locate the child RTE and RelOptInfo */ + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + childrel = root->simple_rel_array[childRTindex]; + subroot = root->inh_target_child_roots[childRTindex]; + /* Transfer the value from main root to subroot. */ + subroot->total_table_pages = root->total_table_pages; + + /* + * If set_append_rel_size() decided the parent appendrel was + * parallel-unsafe at some point after visiting this child rel, we + * need to propagate the unsafety marking down to the child, so that + * we don't generate useless partial paths for it. + */ + if (!rel->consider_parallel) + childrel->consider_parallel = false; + + /* + * Compute the child's access paths. + */ + set_rel_pathlist(subroot, childrel, childRTindex, childRTE); + } +} + +/* * set_append_rel_size * Set size estimates for a simple "append relation" * @@ -1031,6 +1413,13 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); + if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) + { + /* This partition was pruned; skip it. */ + set_dummy_rel_pathlist(childrel); + continue; + } + /* * We have to copy the parent's targetlist and quals to the child, * with appropriate substitution of variables. However, only the @@ -1153,13 +1542,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, continue; } - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - if (relation_excluded_by_constraints(root, childrel, childRTE)) { /* @@ -1198,7 +1580,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, * paths that produce those sort orderings). */ if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) - add_child_rel_equivalences(root, appinfo, rel, childrel); + add_child_rel_equivalences(root, appinfo, rel, childrel, false); childrel->has_eclass_joins = rel->has_eclass_joins; /* @@ -2669,6 +3051,158 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows) } /* + * inheritance_make_rel_from_joinlist + * Perform join planning for all non-dummy leaf inheritance children + * in their role as an UPDATE/DELETE query's target relation + * + * If a child relation is a partitioned table, its children are processed in + * turn by recursively calling this function. + */ +static RelOptInfo * +inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) +{ + Index resultRelation = root->parse->resultRelation; + RelOptInfo *resultrel; + ListCell *lc; +#ifdef USE_ASSERT_CHECKING + Relids all_baserels; +#endif + + /* For UPDATE/DELETE queries, the top parent can only ever be a table. */ + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + Assert(planner_rt_fetch(resultRelation, root)->rtekind == RTE_RELATION); + resultrel = find_base_rel(root, resultRelation); + + /* Nothing to do. */ + if (IS_DUMMY_REL(resultrel)) + return resultrel; + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + PlannerInfo *subroot; + RelOptInfo *childrel; + RelOptInfo *childjoinrel; + List *translated_joinlist; + + if (appinfo->parent_relid != resultRelation) + continue; + + childrel = find_base_rel(root, appinfo->child_relid); + + /* Ignore excluded/pruned children. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* Add this child. */ + root->inh_target_child_rels = lappend_int(root->inh_target_child_rels, + appinfo->child_relid); + + /* Perform join planning with child subroot. */ + subroot = root->inh_target_child_roots[appinfo->child_relid]; + Assert(subroot->parse->resultRelation > 0); + + /* + * Modify joinlist such that relations joined to the top parent rel + * appear to be joined to the child rel instead. Do the same for + * any SpecialJoinInfo structs. + */ + translated_joinlist = (List *) + adjust_appendrel_attrs(subroot, + (Node *) joinlist, + 1, &appinfo); + subroot->join_info_list = (List *) + adjust_appendrel_attrs(subroot, + (Node *) root->join_info_list, + 1, &appinfo); + + /* + * Sub-partitioned tables have to be processed recursively using the + * translated subroot as the parent, because AppendRelInfos link + * sub-partitions to their immediate parents, not the root partitioned + * table. + */ + if (childrel->part_scheme != NULL) + { + /* + * inheritance_make_rel_from_joinlist() return the target relation + * RelOptInfo. + */ + childrel = + inheritance_make_rel_from_joinlist(subroot, + translated_joinlist); + + /* + * Add this child relation as a placeholder in the parent root's + * inh_target_child_path_rels so that inheritance_planner sees + * same number of entries in it as inh_target_child_rels. + */ + root->inh_target_child_path_rels = + lappend(root->inh_target_child_path_rels, childrel); + + /* + * Also propagate this child's own children into the parent's + * list. + */ + if (subroot->inh_target_child_rels != NIL) + { + root->inh_target_child_rels = + list_concat(root->inh_target_child_rels, + subroot->inh_target_child_rels); + root->inh_target_child_path_rels = + list_concat(root->inh_target_child_path_rels, + subroot->inh_target_child_path_rels); + } + continue; + } + + /* + * Since we added the child rel directly into the join tree, we must + * modify it to be a "base" rel instead of an "other" rel, which the + * join planning code expects the relations being joined to be. + */ + childrel->reloptkind = RELOPT_BASEREL; + + Assert(subroot->join_rel_list == NIL); + Assert(subroot->join_rel_hash == NULL); + + /* Perform join planning and save the resulting RelOptInfo. */ + childjoinrel = make_rel_from_joinlist(subroot, translated_joinlist); + + /* Check that we got at least one usable path */ + if (!childjoinrel || !childjoinrel->cheapest_total_path || + childjoinrel->cheapest_total_path->param_info != NULL) + elog(ERROR, "failed to construct the child join relation"); + + /* + * Remember the paths of child target rel. inheritance_planner will + * perform the remaining steps of planning for each child relation + * separately. Specifically, it will call grouping_planner on every + * RelOptInfo contained in the inh_target_child_rels list, each of + * which represents the source of tuples to be modified for a given + * target child rel. + */ + root->inh_target_child_path_rels = + lappend(root->inh_target_child_path_rels, childjoinrel); +#ifdef USE_ASSERT_CHECKING + /* + * The following implements essentially the same Assert as in + * make_one_rel, our caller. + */ + all_baserels = bms_copy(root->all_baserels); + all_baserels = bms_del_member(all_baserels, + root->parse->resultRelation); + all_baserels = bms_add_member(all_baserels, + subroot->parse->resultRelation); + Assert(bms_equal(childjoinrel->relids, all_baserels)); +#endif + } + + return resultrel; +} + +/* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. * diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 6e134ae1d2..8e312d064a 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -2104,12 +2104,18 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root, * * parent_rel and child_rel could be derived from appinfo, but since the * caller has already computed them, we might as well just pass them in. + * + * If 'replace' is true then the translated child EC members *replace* the + * corresponding parent members. For this function to be able to make such + * in-place updates to individual ECs, callers should've made copies of all + * the ECs before calling here. */ void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, - RelOptInfo *child_rel) + RelOptInfo *child_rel, + bool replace) { ListCell *lc1; @@ -2117,14 +2123,23 @@ add_child_rel_equivalences(PlannerInfo *root, { EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc1); ListCell *lc2; + ListCell *prev; + ListCell *next; /* * If this EC contains a volatile expression, then generating child * EMs would be downright dangerous, so skip it. We rely on a - * volatile EC having only one EM. + * volatile EC having only one EM. If the caller asked to *replace* + * the original parent expression with the child one, then it's + * okay to proceed, because we wouldn't be adding another expression + * as being equivalent in that case. */ if (cur_ec->ec_has_volatile) - continue; + { + Assert(list_length(cur_ec->ec_members) == 1); + if (!replace) + continue; + } /* * No point in searching if parent rel not mentioned in eclass; but we @@ -2134,12 +2149,18 @@ add_child_rel_equivalences(PlannerInfo *root, !bms_is_subset(parent_rel->relids, cur_ec->ec_relids)) continue; - foreach(lc2, cur_ec->ec_members) + prev = NULL; + for (lc2 = list_head(cur_ec->ec_members); lc2 != NULL; lc2 = next) { EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2); + next = lnext(lc2); + if (cur_em->em_is_const) + { + prev = lc2; continue; /* ignore consts here */ + } /* Does it reference parent_rel? */ if (bms_overlap(cur_em->em_relids, parent_rel->relids)) @@ -2177,10 +2198,38 @@ add_child_rel_equivalences(PlannerInfo *root, child_rel->relids); } + /* + * Caller may have asked us to replace the parent expression + * with the child expression, so delete the parent expression + * first. + */ + if (replace) + cur_ec->ec_members = list_delete_cell(cur_ec->ec_members, + lc2, prev); + + /* + * If we're replacing, then the new member isn't really a + * a child, so em_is_child should be set to false. + */ (void) add_eq_member(cur_ec, child_expr, new_relids, new_nullable_relids, - true, cur_em->em_datatype); + !replace, cur_em->em_datatype); } + else + prev = lc2; + } + + if (replace) + { + /* + * Now fix up EC's relids set. It's OK to modify EC like this, + * because caller must have made a copy of the original EC. + * For example, see adjust_inherited_target_child_root. + */ + cur_ec->ec_relids = bms_del_members(cur_ec->ec_relids, + parent_rel->relids); + cur_ec->ec_relids = bms_add_members(cur_ec->ec_relids, + child_rel->relids); } } } diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 8bfe9c3ff7..663130461a 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1463,7 +1463,7 @@ update_child_rel_info(PlannerInfo *root, /* Make child entries in the EquivalenceClass as well */ if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) - add_child_rel_equivalences(root, appinfo, rel, childrel); + add_child_rel_equivalences(root, appinfo, rel, childrel, false); childrel->has_eclass_joins = rel->has_eclass_joins; } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index c1aa0ba663..66e683d72a 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -2050,12 +2050,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) /* * During setrefs.c, we'll need the grouping_map to fix up the cols lists * in GroupingFunc nodes. Save it for setrefs.c to use. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be grouping in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->grouping_map == NULL); root->grouping_map = grouping_map; @@ -2212,12 +2207,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) * with InitPlan output params. (We can't just do that locally in the * MinMaxAgg node, because path nodes above here may have Agg references * as well.) Save the mmaggregates list to tell setrefs.c to do that. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be aggregates in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->minmax_aggs == NIL); root->minmax_aggs = best_path->mmaggregates; diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index f9f4b12db7..e536828e2c 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -28,7 +28,7 @@ #include "optimizer/paths.h" #include "optimizer/placeholder.h" #include "optimizer/planmain.h" - +#include "optimizer/prep.h" /* * query_planner @@ -59,6 +59,7 @@ query_planner(PlannerInfo *root, List *tlist, Query *parse = root->parse; List *joinlist; RelOptInfo *final_rel; + Index rti; /* * Init planner lists to empty. @@ -259,14 +260,42 @@ query_planner(PlannerInfo *root, List *tlist, extract_restriction_or_clauses(root); /* + * Construct the all_baserels Relids set. + */ + root->all_baserels = NULL; + for (rti = 1; rti < root->simple_rel_array_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + Assert(brel->relid == rti); /* sanity check on array */ + + /* ignore RTEs that are "other rels" */ + if (brel->reloptkind != RELOPT_BASEREL) + continue; + + root->all_baserels = bms_add_member(root->all_baserels, brel->relid); + } + + /* + * Add child subroots needed to use during planning for individual child + * targets + */ + if (root->inherited_update) + { + root->inh_target_child_roots = (PlannerInfo **) + palloc0(root->simple_rel_array_size * + sizeof(PlannerInfo *)); + add_inherited_target_child_roots(root); + } + + /* * Ready to do the primary planning. */ final_rel = make_one_rel(root, joinlist); - /* Check that we got at least one usable path */ - if (!final_rel || !final_rel->cheapest_total_path || - final_rel->cheapest_total_path->param_info != NULL) - elog(ERROR, "failed to construct the join relation"); - return final_rel; } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 272710eed2..7c53785c55 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -39,6 +39,7 @@ #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif +#include "nodes/relation.h" #include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" @@ -131,7 +132,7 @@ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static void inheritance_planner(PlannerInfo *root); static void grouping_planner(PlannerInfo *root, bool inheritance_update, - double tuple_fraction); + RelOptInfo *planned_rel, double tuple_fraction); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, int *tleref_to_colnum_map); @@ -636,7 +637,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->grouping_map = NULL; root->minmax_aggs = NIL; root->qual_security_level = 0; - root->inhTargetKind = INHKIND_NONE; root->hasRecursion = hasRecursion; if (hasRecursion) root->wt_param_id = assign_special_exec_param(root); @@ -738,6 +738,13 @@ subquery_planner(PlannerGlobal *glob, Query *parse, expand_inherited_tables(root); /* + * Now that we have figured out "actual" inheritance situation of the + * relations, set whether the query is an inherited UPDATE/DELETE. + */ + root->inherited_update = (parse->resultRelation && + rt_fetch(parse->resultRelation, parse->rtable)->inh); + + /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -996,7 +1003,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh) inheritance_planner(root); else - grouping_planner(root, false, tuple_fraction); + grouping_planner(root, false, NULL, tuple_fraction); /* * Capture the set of outer-level param IDs we have access to, for use in @@ -1172,13 +1179,41 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr) * inheritance set. * * We have to handle this case differently from cases where a source relation - * is an inheritance set. Source inheritance is expanded at the bottom of the - * plan tree (see allpaths.c), but target inheritance has to be expanded at - * the top. The reason is that for UPDATE, each target relation needs a - * different targetlist matching its own column set. Fortunately, + * is an inheritance set, where outputs of all the children are combined and + * combined output consumed using the source relation's (parent's) column set. + * For UPDATE, each target relation, where the query's output will go, needs + * a different targetlist matching its own column set. So, we will need to + * translate the query such that it produces the desired output required by + * each of the child target relations and plan each separately. Fortunately, * the UPDATE/DELETE target can never be the nullable side of an outer join, * so it's OK to generate the plan this way. * + * Planning steps that need to be performed separately for each child and + * any preliminary processing needed for that are handled by the following + * sub-routines of query_planner: + * + * add_inherit_target_roots - this creates copies of PlannerInfo for each + * child after query_planner has finished processing the join tree and + * creating EquivalenceClasses, so that working state of planning need + * not be created afresh for each child, especially the various arrays + * + * set_inherit_target_rel_sizes - this sets size estimates for child + * relations, replace the parent EC members by corresponding child ones + * in their respective subroots + * + * set_inherit_target_rel_pathlists - this creates Paths for scanning + * individual child relations + * + * inherit_make_rel_from_joinlist - this translates the jointree, replacing + * the target relation in the original jointree (the root parent) by + * individual child target relations and performs join planning on the + * resulting join tree, saving the RelOptInfos of resulting join relations + * into the top-level PlannerInfo + * + * Finally, here we apply grouping_planner to each child join relation + * produced by set_inherit_target_rel_pathlists to so that its path produces + * the desired targetlist. + * * Returns nothing; the useful output is in the Paths we attach to * the (UPPERREL_FINAL, NULL) upperrel stored in *root. * @@ -1190,14 +1225,8 @@ inheritance_planner(PlannerInfo *root) { Query *parse = root->parse; int top_parentRTindex = parse->resultRelation; - Bitmapset *subqueryRTindexes; - Bitmapset *modifiableARIindexes; int nominalRelation = -1; Index rootRelation = 0; - List *final_rtable = NIL; - int save_rel_array_size = 0; - RelOptInfo **save_rel_array = NULL; - AppendRelInfo **save_append_rel_array = NULL; List *subpaths = NIL; List *subroots = NIL; List *resultRelations = NIL; @@ -1205,70 +1234,47 @@ inheritance_planner(PlannerInfo *root) List *returningLists = NIL; List *rowMarks; RelOptInfo *final_rel; - ListCell *lc; - Index rti; + ListCell *lc1, + *lc2; RangeTblEntry *parent_rte; - PlannerInfo *parent_root; - Query *parent_parse; - Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); - PlannerInfo **parent_roots = NULL; + List *tlist; + standard_qp_extra qp_extra; + RelOptInfo *planned_rel; + /* Inheritance is never used for insert. */ Assert(parse->commandType != CMD_INSERT); /* - * We generate a modified instance of the original Query for each target - * relation, plan that, and put all the plans into a list that will be - * controlled by a single ModifyTable node. All the instances share the - * same rangetable, but each instance must have its own set of subquery - * RTEs within the finished rangetable because (1) they are likely to get - * scribbled on during planning, and (2) it's not inconceivable that - * subqueries could get planned differently in different cases. We need - * not create duplicate copies of other RTE kinds, in particular not the - * target relations, because they don't have either of those issues. Not - * having to duplicate the target relations is important because doing so - * (1) would result in a rangetable of length O(N^2) for N targets, with - * at least O(N^3) work expended here; and (2) would greatly complicate - * management of the rowMarks list. + * Let query_planner generate the access paths for the query for each + * target child relation. * - * To begin with, generate a bitmapset of the relids of the subquery RTEs. + * First, save the unexpanded version of the query's targetlist. + * create_inherit_target_child_root will use it as base when expanding + * it for a given child relation as the query's target relation instead + * of the parent. */ - subqueryRTindexes = NULL; - rti = 1; - foreach(lc, parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - if (rte->rtekind == RTE_SUBQUERY) - subqueryRTindexes = bms_add_member(subqueryRTindexes, rti); - rti++; - } + root->unexpanded_tlist = list_copy(root->parse->targetList); + tlist = preprocess_targetlist(root); + root->processed_tlist = tlist; + qp_extra.tlist = tlist; + qp_extra.activeWindows = NIL; + qp_extra.groupClause = NIL; + planned_rel = query_planner(root, tlist, standard_qp_callback, &qp_extra); /* - * Next, we want to identify which AppendRelInfo items contain references - * to any of the aforesaid subquery RTEs. These items will need to be - * copied and modified to adjust their subquery references; whereas the - * other ones need not be touched. It's worth being tense over this - * because we can usually avoid processing most of the AppendRelInfo - * items, thereby saving O(N^2) space and time when the target is a large - * inheritance tree. We can identify AppendRelInfo items by their - * child_relid, since that should be unique within the list. + * If it turned out during query planning that all the children are dummy + * (pruned or excluded by constraints), no need to do the steps below. + * Let grouping_planner finish up the final path. */ - modifiableARIindexes = NULL; - if (subqueryRTindexes != NULL) + if (IS_DUMMY_REL(planned_rel)) { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - - if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) || - bms_is_member(appinfo->child_relid, subqueryRTindexes) || - bms_overlap(pull_varnos((Node *) appinfo->translated_vars), - subqueryRTindexes)) - modifiableARIindexes = bms_add_member(modifiableARIindexes, - appinfo->child_relid); - } + grouping_planner(root, false, planned_rel, 0.0); + return; } + Assert(planned_rel->relid == top_parentRTindex); + Assert(planned_rel->reloptkind == RELOPT_BASEREL); + /* * If the parent RTE is a partitioned table, we should use that as the * nominal target relation, because the RTEs added for partitioned tables @@ -1276,7 +1282,7 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); + parent_rte = planner_rt_fetch(top_parentRTindex, root); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1284,95 +1290,39 @@ inheritance_planner(PlannerInfo *root) } /* - * The PlannerInfo for each child is obtained by translating the relevant - * members of the PlannerInfo for its immediate parent, which we find - * using the parent_relid in its AppendRelInfo. We save the PlannerInfo - * for each parent in an array indexed by relid for fast retrieval. Since - * the maximum number of parents is limited by the number of RTEs in the - * query, we use that number to allocate the array. An extra entry is - * needed since relids start from 1. + * Get on with finalizing the path for each child target relation by + * calling grouping_planner on its joinrel. */ - parent_roots = (PlannerInfo **) palloc0((list_length(parse->rtable) + 1) * - sizeof(PlannerInfo *)); - parent_roots[top_parentRTindex] = root; - - /* - * And now we can get on with generating a plan for each child table. - */ - foreach(lc, root->append_rel_list) + forboth(lc1, root->inh_target_child_rels, + lc2, root->inh_target_child_path_rels) { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - PlannerInfo *subroot; + Index childRTindex = lfirst_int(lc1); + RelOptInfo *childjoinrel = lfirst(lc2); + PlannerInfo *subroot = root->inh_target_child_roots[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; RangeTblEntry *child_rte; RelOptInfo *sub_final_rel; Path *subpath; - /* append_rel_list contains all append rels; ignore others */ - if (!bms_is_member(appinfo->parent_relid, parent_relids)) - continue; - - /* - * expand_inherited_rtentry() always processes a parent before any of - * that parent's children, so the parent_root for this relation should - * already be available. - */ - parent_root = parent_roots[appinfo->parent_relid]; - Assert(parent_root != NULL); - parent_parse = parent_root->parse; - - /* - * We need a working copy of the PlannerInfo so that we can control - * propagation of information back to the main copy. - */ - subroot = makeNode(PlannerInfo); - memcpy(subroot, parent_root, sizeof(PlannerInfo)); - - /* - * Generate modified query with this rel as target. We first apply - * adjust_appendrel_attrs, which copies the Query and changes - * references to the parent RTE to refer to the current child RTE, - * then fool around with subquery RTEs. - */ - subroot->parse = (Query *) - adjust_appendrel_attrs(parent_root, - (Node *) parent_parse, - 1, &appinfo); + Assert(subroot != NULL); + Assert(subroot->parse->resultRelation == childRTindex); /* * If there are securityQuals attached to the parent, move them to the * child rel (they've already been transformed properly for that). */ - parent_rte = rt_fetch(appinfo->parent_relid, subroot->parse->rtable); - child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable); + parent_rte = planner_rt_fetch(appinfo->parent_relid, subroot); + child_rte = planner_rt_fetch(appinfo->child_relid, subroot); child_rte->securityQuals = parent_rte->securityQuals; parent_rte->securityQuals = NIL; /* - * Mark whether we're planning a query to a partitioned table or an - * inheritance parent. + * Ignore a partitioned child. Instead, the paths of its children will + * be added to subpaths. */ - subroot->inhTargetKind = - (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED; - - /* - * If this child is further partitioned, remember it as a parent. - * Since a partitioned table does not have any data, we don't need to - * create a plan for it, and we can stop processing it here. We do, - * however, need to remember its modified PlannerInfo for use when - * processing its children, since we'll update their varnos based on - * the delta from immediate parent to child, not from top to child. - * - * Note: a very non-obvious point is that we have not yet added - * duplicate subquery RTEs to the subroot's rtable. We mustn't, - * because then its children would have two sets of duplicates, - * confusing matters. - */ - if (child_rte->inh) + if (childjoinrel->part_scheme) { - Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE); - parent_relids = bms_add_member(parent_relids, appinfo->child_relid); - parent_roots[appinfo->child_relid] = subroot; - + Assert(child_rte->inh); continue; } @@ -1400,111 +1350,8 @@ inheritance_planner(PlannerInfo *root) if (nominalRelation < 0) nominalRelation = appinfo->child_relid; - /* - * The rowMarks list might contain references to subquery RTEs, so - * make a copy that we can apply ChangeVarNodes to. (Fortunately, the - * executor doesn't need to see the modified copies --- we can just - * pass it the original rowMarks list.) - */ - subroot->rowMarks = copyObject(parent_root->rowMarks); - - /* - * The append_rel_list likewise might contain references to subquery - * RTEs (if any subqueries were flattenable UNION ALLs). So prepare - * to apply ChangeVarNodes to that, too. As explained above, we only - * want to copy items that actually contain such references; the rest - * can just get linked into the subroot's append_rel_list. - * - * If we know there are no such references, we can just use the outer - * append_rel_list unmodified. - */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - subroot->append_rel_list = NIL; - foreach(lc2, parent_root->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, modifiableARIindexes)) - appinfo2 = copyObject(appinfo2); - - subroot->append_rel_list = lappend(subroot->append_rel_list, - appinfo2); - } - } - - /* - * Add placeholders to the child Query's rangetable list to fill the - * RT indexes already reserved for subqueries in previous children. - * These won't be referenced, so there's no need to make them very - * valid-looking. - */ - while (list_length(subroot->parse->rtable) < list_length(final_rtable)) - subroot->parse->rtable = lappend(subroot->parse->rtable, - makeNode(RangeTblEntry)); - - /* - * If this isn't the first child Query, generate duplicates of all - * subquery RTEs, and adjust Var numbering to reference the - * duplicates. To simplify the loop logic, we scan the original rtable - * not the copy just made by adjust_appendrel_attrs; that should be OK - * since subquery RTEs couldn't contain any references to the target - * rel. - */ - if (final_rtable != NIL && subqueryRTindexes != NULL) - { - ListCell *lr; - - rti = 1; - foreach(lr, parent_parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lr); - - if (bms_is_member(rti, subqueryRTindexes)) - { - Index newrti; - - /* - * The RTE can't contain any references to its own RT - * index, except in its securityQuals, so we can save a - * few cycles by applying ChangeVarNodes to the rest of - * the rangetable before we append the RTE to it. - */ - newrti = list_length(subroot->parse->rtable) + 1; - ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); - ChangeVarNodes((Node *) subroot->rowMarks, rti, newrti, 0); - /* Skip processing unchanging parts of append_rel_list */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - foreach(lc2, subroot->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, - modifiableARIindexes)) - ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); - } - } - rte = copyObject(rte); - ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); - subroot->parse->rtable = lappend(subroot->parse->rtable, - rte); - } - rti++; - } - } - - /* There shouldn't be any OJ info to translate, as yet */ - Assert(subroot->join_info_list == NIL); - /* and we haven't created PlaceHolderInfos, either */ - Assert(subroot->placeholder_list == NIL); - - /* Generate Path(s) for accessing this result relation */ - grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ ); + /* Apply the top-level targetlist to childjoinrel's path. */ + grouping_planner(subroot, true, childjoinrel, 0.0); /* * Select cheapest path in case there's more than one. We always run @@ -1516,45 +1363,10 @@ inheritance_planner(PlannerInfo *root) subpath = sub_final_rel->cheapest_total_path; /* - * If this child rel was excluded by constraint exclusion, exclude it - * from the result plan. + * child rel cannot be empty, or inheritance_make_rel_from_joinlist + * wouldn't have put it in the list to begin with. */ - if (IS_DUMMY_PATH(subpath)) - continue; - - /* - * If this is the first non-excluded child, its post-planning rtable - * becomes the initial contents of final_rtable; otherwise, append - * just its modified subquery RTEs to final_rtable. - */ - if (final_rtable == NIL) - final_rtable = subroot->parse->rtable; - else - final_rtable = list_concat(final_rtable, - list_copy_tail(subroot->parse->rtable, - list_length(final_rtable))); - - /* - * We need to collect all the RelOptInfos from all child plans into - * the main PlannerInfo, since setrefs.c will need them. We use the - * last child's simple_rel_array (previous ones are too short), so we - * have to propagate forward the RelOptInfos that were already built - * in previous children. - */ - Assert(subroot->simple_rel_array_size >= save_rel_array_size); - for (rti = 1; rti < save_rel_array_size; rti++) - { - RelOptInfo *brel = save_rel_array[rti]; - - if (brel) - subroot->simple_rel_array[rti] = brel; - } - save_rel_array_size = subroot->simple_rel_array_size; - save_rel_array = subroot->simple_rel_array; - save_append_rel_array = subroot->append_rel_array; - - /* Make sure any initplans from this rel get into the outer list */ - root->init_plans = subroot->init_plans; + Assert(!IS_DUMMY_PATH(subpath)); /* Build list of sub-paths */ subpaths = lappend(subpaths, subpath); @@ -1586,36 +1398,6 @@ inheritance_planner(PlannerInfo *root) */ /* - * If we managed to exclude every child rel, return a dummy plan; it - * doesn't even need a ModifyTable node. - */ - if (subpaths == NIL) - { - set_dummy_rel_pathlist(final_rel); - return; - } - - /* - * Put back the final adjusted rtable into the master copy of the Query. - * (We mustn't do this if we found no non-excluded children.) - */ - parse->rtable = final_rtable; - root->simple_rel_array_size = save_rel_array_size; - root->simple_rel_array = save_rel_array; - root->append_rel_array = save_append_rel_array; - - /* Must reconstruct master's simple_rte_array, too */ - root->simple_rte_array = (RangeTblEntry **) - palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *)); - rti = 1; - foreach(lc, final_rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - root->simple_rte_array[rti++] = rte; - } - - /* * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will * have dealt with fetching non-locked marked rows, else we need to have * ModifyTable do that. @@ -1655,6 +1437,12 @@ inheritance_planner(PlannerInfo *root) * (inheritance_planner will create a single ModifyTable node covering all the * target tables.) * + * If non-NULL, planned_rel is a RelOptInfo containing paths for the query's + * top-level joinrel, which the caller produced by itself. In that case, this + * function only needs to adjust the targetlist of its cheapest_total_path. + * The only caller that may pass such a RelOptInfo currently is + * inheritance_planner. + * * tuple_fraction is the fraction of tuples we expect will be retrieved. * tuple_fraction is interpreted as follows: * 0: expect all tuples to be retrieved (normal case) @@ -1673,6 +1461,7 @@ inheritance_planner(PlannerInfo *root) */ static void grouping_planner(PlannerInfo *root, bool inheritance_update, + RelOptInfo *planned_rel, double tuple_fraction) { Query *parse = root->parse; @@ -1685,7 +1474,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, List *final_targets; List *final_targets_contain_srfs; bool final_target_parallel_safe; - RelOptInfo *current_rel; + RelOptInfo *current_rel = planned_rel; RelOptInfo *final_rel; ListCell *lc; @@ -1725,6 +1514,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * special work for recursive unions is the responsibility of * plan_set_operations. */ + Assert(current_rel == NULL); current_rel = plan_set_operations(root); /* @@ -1814,17 +1604,26 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, parse->groupClause = preprocess_groupclause(root, NIL); } - /* Preprocess targetlist */ - tlist = preprocess_targetlist(root); - /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation so - * that we can transfer its decoration (resnames etc) to the topmost - * tlist of the finished Plan. + * Preprocess targetlist, if needed. If the caller has already done + * query planning, root->processed_tlist already contains the desired + * targetlist. */ - root->processed_tlist = tlist; + if (planned_rel == NULL) + { + tlist = preprocess_targetlist(root); + + /* + * We are now done hacking up the query's targetlist. Most of the + * remaining planning work will be done with the PathTarget + * representation of tlists, but save aside the full representation + * so that we can transfer its decoration (resnames etc) to the + * topmost tlist of the finished Plan. + */ + root->processed_tlist = tlist; + } + else + tlist = root->processed_tlist; /* * Collect statistics about aggregates for estimating costs, and mark @@ -1904,8 +1703,9 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * We also generate (in standard_qp_callback) pathkey representations * of the query's sort clause, distinct clause, etc. */ - current_rel = query_planner(root, tlist, - standard_qp_callback, &qp_extra); + if (current_rel == NULL) + current_rel = query_planner(root, tlist, + standard_qp_callback, &qp_extra); /* * Convert the query's result tlist into PathTarget format. diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index bcbca1a0f5..9dc73b5108 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -894,7 +894,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->grouping_map = NULL; subroot->minmax_aggs = NIL; subroot->qual_security_level = 0; - subroot->inhTargetKind = INHKIND_NONE; subroot->hasRecursion = false; subroot->wt_param_id = -1; subroot->non_recursive_path = NULL; diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index ca6622ece9..34a3ebbbe4 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -394,8 +394,39 @@ adjust_appendrel_attrs_mutator(Node *node, context->appinfos); return (Node *) phv; } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index eaf788e578..56b1870a6f 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -21,11 +21,14 @@ #include "miscadmin.h" #include "optimizer/appendinfo.h" #include "optimizer/inherit.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "utils/rel.h" +static PlannerInfo *create_inherited_target_child_root(PlannerInfo *root, + AppendRelInfo *appinfo); static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti); static void expand_partitioned_rtentry(PlannerInfo *root, @@ -492,3 +495,150 @@ translate_col_privs(const Bitmapset *parent_privs, return child_privs; } + +/* + * add_inherited_target_child_roots + * For each child of the query's result relation, this translates the + * original query to match the child and creates a PlannerInfo containing + * the translated query + * + * Child PlannerInfo reuses most of the parent PlannerInfo's fields unchanged, + * except unexpanded_tlist, processed_tlist, and all_baserels, all of which + * are based on the child relation. + */ +void +add_inherited_target_child_roots(PlannerInfo *root) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; + + Assert(root->inh_target_child_roots != NULL); + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + RangeTblEntry *childRTE; + PlannerInfo *subroot; + + if (appinfo->parent_relid != resultRelation) + continue; + + /* + * Create a PlannerInfo for processing this child target relation + * with. + */ + subroot = create_inherited_target_child_root(root, appinfo); + root->inh_target_child_roots[appinfo->child_relid] = subroot; + + /* + * If the child is a partitioned table, recurse to do this for its + * partitions. + */ + childRTE = root->simple_rte_array[appinfo->child_relid]; + if (childRTE->inh) + add_inherited_target_child_roots(subroot); + } +} + +/* + * create_inherited_target_child_root + * Workhorse of add_inherited_target_child_roots + */ +static PlannerInfo * +create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) +{ + PlannerInfo *subroot; + List *tlist; + + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + + /* + * Translate the original query to replace Vars of the parent table + * by the corresponding Vars of the child table and to make child the main + * target relation of the query. + */ + subroot = makeNode(PlannerInfo); + memcpy(subroot, root, sizeof(PlannerInfo)); + + /* + * Restore the original, unexpanded targetlist, that is, the one before + * preprocess_targetlist was run on the original query. We'll run + * preprocess_targetlist after translating the query and the targetlist, + * so that it is expanded according to child's tuple descriptor. + */ + root->parse->targetList = root->unexpanded_tlist; + subroot->parse = (Query *) adjust_appendrel_attrs(root, + (Node *) root->parse, + 1, &appinfo); + + /* + * Save the just translated targetlist as unexpanded_tlist in the child's + * subroot, so that this child's own children can use it. Must use copy + * because subroot->parse->targetList will be modified soon. + */ + subroot->unexpanded_tlist = list_copy(subroot->parse->targetList); + + /* + * Apply planner's expansion of targetlist, such as adding various junk + * column, filling placeholder entries for dropped columns, etc., all of + * which occurs with the child's TupleDesc. + */ + tlist = preprocess_targetlist(subroot); + subroot->processed_tlist = tlist; + + /* Add any newly added Vars to the child RelOptInfo. */ + build_base_rel_tlists(subroot, tlist); + + /* + * Adjust all_baserels to replace the original target relation with the + * child target relation. Copy it before modifying though. + */ + subroot->all_baserels = bms_copy(root->all_baserels); + subroot->all_baserels = bms_del_member(subroot->all_baserels, + root->parse->resultRelation); + subroot->all_baserels = bms_add_member(subroot->all_baserels, + subroot->parse->resultRelation); + + return subroot; +} + +/* + * copy_eq_classes_for_child_root + * This creates a copy of the planner's EC data structures for assigning + * to a inheritance child subroot + */ +List * +copy_eq_classes_for_child_root(PlannerInfo *root) +{ + List *result = NIL; + ListCell *lc; + + foreach(lc, root->eq_classes) + { + EquivalenceClass *ec = lfirst(lc); + EquivalenceClass *new_ec = makeNode(EquivalenceClass); + + /* + * First memcpy the existing EC to the new EC, which creates shallow + * copies of all the members and then make copies of members that + * could change by child planning. + * + * XXX comment in _copyPathKey says it's OK to recycle EC + * pointers, but as long as we do the whole planning for a + * given child using a given root, copying ECs like this + * shouldn't be a problem. Maybe, the following code could + * be in _copyEquivalenceClass()? + */ + memcpy(new_ec, ec, sizeof(EquivalenceClass)); + new_ec->ec_opfamilies = list_copy(ec->ec_opfamilies); + new_ec->ec_members = list_copy(ec->ec_members); + new_ec->ec_sources = list_copy(ec->ec_sources); + new_ec->ec_derives = list_copy(ec->ec_derives); + new_ec->ec_relids = bms_copy(ec->ec_relids); + + result = lappend(result, new_ec); + } + + return result; +} diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 243344a011..9aa819c002 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1264,36 +1264,6 @@ get_relation_constraints(PlannerInfo *root, } } - /* - * Append partition predicates, if any. - * - * For selects, partition pruning uses the parent table's partition bound - * descriptor, instead of constraint exclusion which is driven by the - * individual partition's partition constraint. - */ - if (enable_partition_pruning && root->parse->commandType != CMD_SELECT) - { - List *pcqual = RelationGetPartitionQual(relation); - - if (pcqual) - { - /* - * Run the partition quals through const-simplification similar to - * check constraints. We skip canonicalize_qual, though, because - * partition quals should be in canonical form already; also, - * since the qual is in implicit-AND format, we'd have to - * explicitly convert it to explicit-AND format and back again. - */ - pcqual = (List *) eval_const_expressions(root, (Node *) pcqual); - - /* Fix Vars to have the desired varno */ - if (varno != 1) - ChangeVarNodes((Node *) pcqual, 1, varno, 0); - - result = list_concat(result, pcqual); - } - } - table_close(relation, NoLock); return result; @@ -1417,40 +1387,15 @@ relation_excluded_by_constraints(PlannerInfo *root, /* * Skip further tests, depending on constraint_exclusion. */ - switch (constraint_exclusion) - { - case CONSTRAINT_EXCLUSION_OFF: - - /* - * Don't prune if feature turned off -- except if the relation is - * a partition. While partprune.c-style partition pruning is not - * yet in use for all cases (update/delete is not handled), it - * would be a UI horror to use different user-visible controls - * depending on such a volatile implementation detail. Therefore, - * for partitioned tables we use enable_partition_pruning to - * control this behavior. - */ - if (root->inhTargetKind == INHKIND_PARTITIONED) - break; - return false; - - case CONSTRAINT_EXCLUSION_PARTITION: - - /* - * When constraint_exclusion is set to 'partition' we only handle - * OTHER_MEMBER_RELs, or BASERELs in cases where the result target - * is an inheritance parent or a partitioned table. - */ - if ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) && - !(rel->reloptkind == RELOPT_BASEREL && - root->inhTargetKind != INHKIND_NONE && - rel->relid == root->parse->resultRelation)) - return false; - break; - - case CONSTRAINT_EXCLUSION_ON: - break; /* always try to exclude */ - } + if (constraint_exclusion == CONSTRAINT_EXCLUSION_OFF) + return false; + /* + * When constraint_exclusion is set to 'partition' we only handle + * OTHER_MEMBER_RELs. + */ + else if (constraint_exclusion == CONSTRAINT_EXCLUSION_PARTITION && + rel->reloptkind != RELOPT_OTHER_MEMBER_REL) + return false; /* * Check for self-contradictory restriction clauses. We dare not make diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 420ca05c30..c208fe1533 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -319,9 +319,6 @@ typedef struct PlannerInfo Index qual_security_level; /* minimum security_level for quals */ /* Note: qual_security_level is zero if there are no securityQuals */ - InheritanceKind inhTargetKind; /* indicates if the target relation is an - * inheritance child or partition or a - * partitioned table */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */ bool hasHavingQual; /* true if havingQual was non-null */ @@ -342,6 +339,41 @@ typedef struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* + * The following fields are set during query planning portion of an + * inherited UPDATE/DELETE operation. + */ + bool inherited_update; /* UPDATE/DELETE on inheritance parent? */ + + /* + * This stores the original version of the query's targetlist that's + * not modified by the planner. + */ + List *unexpanded_tlist; + + /* + * Array containing simple_rel_array_size elements, indexed by rangetable + * index (entry 0 is wasted like simple_rel_array). Only elements + * corresponding to individual inheritance child target relations are + * non-NULL. Content of each PlannerInfo is mostly same as the parent + * PlannerInfo, except for few fields such as the parse tree which is + * a translated copy of the parent's parse tree, EC list which contain + * child member expressions, etc. + */ + struct PlannerInfo **inh_target_child_roots; + + /* List of RT indexes of child target relations. */ + List *inh_target_child_rels; + + /* + * RelOptInfos corresponding to each child target rel. For leaf children, + * it's the RelOptInfo representing the output of make_rel_from_joinlist() + * called with the parent rel in the original join tree replaced by a + * given leaf child. For non-leaf children, it's the baserel RelOptInfo + * itself, left as a placeholder. + */ + List *inh_target_child_path_rels; } PlannerInfo; diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h index b2687abd4e..7c760b8e09 100644 --- a/src/include/optimizer/inherit.h +++ b/src/include/optimizer/inherit.h @@ -18,5 +18,7 @@ extern void expand_inherited_tables(PlannerInfo *root); +extern void add_inherited_target_child_roots(PlannerInfo *root); +extern List *copy_eq_classes_for_child_root(PlannerInfo *root); #endif /* INHERIT_H */ diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 666217c189..f524ab6beb 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -161,7 +161,8 @@ extern EquivalenceClass *match_eclasses_to_foreign_key_col(PlannerInfo *root, extern void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, - RelOptInfo *child_rel); + RelOptInfo *child_rel, + bool replace); extern List *generate_implied_equalities_for_column(PlannerInfo *root, RelOptInfo *rel, ec_matches_callback_type callback, diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index c55de5d476..c9f02c1fb0 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1780,7 +1780,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_1 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_1 -> Nested Loop Semi Join @@ -1788,7 +1788,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_2 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_2 (28 rows) -- 2.11.0
v18-0002-Lazy-creation-of-RTEs-for-inheritance-children.patch (text/plain, 96.2K) download From d140811d044b3511de9bf6a549ca1c8f58fdbac6 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 26 Oct 2018 16:45:59 +0900 Subject: [PATCH v18 2/4] Lazy creation of RTEs for inheritance children Currently, expand_inherited_tables called from subquery_planner opens all inheritance child tables and adds them to the query's range table and PlannerInfo in the form of AppendRelInfo's initially and later RelOptInfo's. For partitioning, that's pretty wasteful, because it's possible to determine only the partitions that will need to be scanned using partition pruning, which doesn't require opening the partitions themselves. However, the point at which expand_inherited_tables is called currently is too early to perform partition pruning. This commit rearranges things within the planner so that expand_inherited_tables can be called from query_planner after the latter has finished setting up the information needed to perform partition pruning. However that means the PlannerInfo arrays that store RangeTblEntry's, RelOptInfo's, and AppendRelInfo's need to be expanded if inheritance expansion leads to adding more relations to planning. There are also consequences around targetlist expansion. For example, preprocess_targetlist cannot add the junk columns entries needed for row mark handling until all of the inheritance children have been determined, because different children may need different types of junk columns to be added. So, the approach of late inheritance expansion required some interface tweaks for preprocess_targetlist(). For partitioning, although we don't create a RangeTblEntry and RelOptInfo for pruned partitions at make_one_rel time, partitionwise join code relies on the fact that even though partitions may have been pruned, they'd still own a RelOptInfo to handle the outer join case where the pruned partition appears on the nullable side of join. Partitionwise join code deals with that by allocating dummy RelOptInfos for pruned partitions that are based mostly on their parent's properties. There are some regression test diffs: 1. Caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child, as seen in the partition_aggregate.out test output. 2. Those in postgres_fdw.out are caused by the fact that junk columns required for row marking are added to reltarget->exprs later than user columns, because the row marking junk columns aren't added until the inheritance is expanded which as of this commit is later than it used to be as noted above. --- contrib/postgres_fdw/expected/postgres_fdw.out | 32 +- src/backend/nodes/outfuncs.c | 1 + src/backend/optimizer/path/allpaths.c | 339 +-------- src/backend/optimizer/path/joinrels.c | 61 ++ src/backend/optimizer/plan/initsplan.c | 58 -- src/backend/optimizer/plan/planmain.c | 10 + src/backend/optimizer/plan/planner.c | 74 +- src/backend/optimizer/plan/setrefs.c | 6 + src/backend/optimizer/prep/preptlist.c | 15 +- src/backend/optimizer/prep/prepunion.c | 3 + src/backend/optimizer/util/appendinfo.c | 45 +- src/backend/optimizer/util/inherit.c | 817 +++++++++++++++------- src/backend/optimizer/util/plancat.c | 47 +- src/backend/optimizer/util/relnode.c | 107 +-- src/backend/partitioning/partprune.c | 43 +- src/include/nodes/relation.h | 18 + src/include/optimizer/appendinfo.h | 7 +- src/include/optimizer/pathnode.h | 1 + src/include/optimizer/plancat.h | 4 +- src/include/optimizer/prep.h | 3 +- src/include/partitioning/partprune.h | 2 +- src/test/regress/expected/partition_aggregate.out | 4 +- 22 files changed, 932 insertions(+), 765 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index b3894d0760..fde1a9cdca 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7072,15 +7072,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7110,15 +7110,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7147,15 +7147,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid @@ -7165,15 +7165,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (39 rows) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index d1e0862c69..b3fb576d13 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2271,6 +2271,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_BOOL_FIELD(consider_partitionwise_join); WRITE_BITMAPSET_FIELD(top_parent_relids); WRITE_NODE_FIELD(partitioned_child_rels); + WRITE_UINT_FIELD(inh_root_parent); } static void diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 435b26e097..913e2c5efc 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -45,7 +45,6 @@ #include "optimizer/var.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -386,6 +385,14 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, else if (rte->inh) { /* + * expand_inherited_tables may have proved that the relation is empty. + * For example, if it's a partitioned table with 0 partitions or all + * of its partitions are pruned. In that case nothing to do here. + */ + if (IS_DUMMY_REL(rel)) + return; + + /* * If it's a target relation, set the sizes of children instead. * Otherwise, we'll append the outputs of children, so process it as * an "append relation". @@ -966,38 +973,18 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, int parentRTindex = rti; bool has_live_children = false; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); Assert(IS_SIMPLE_REL(rel)); - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - foreach(l, root->append_rel_list) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; PlannerInfo *subroot; ListCell *lc; List *translated_exprs, @@ -1009,141 +996,22 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); + Assert(childrel != NULL); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); + /* + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals. + */ + if (IS_DUMMY_REL(childrel)) continue; - } - - /* - * We have to copy the parent's quals to the child, with appropriate - * substitution of variables. However, only the baserestrictinfo - * quals are needed before we can check for constraint exclusion; so - * do that first and then check to see if we can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - continue; - } if (relation_excluded_by_constraints(root, childrel, childRTE)) { @@ -1321,8 +1189,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -1330,32 +1196,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Assert(IS_SIMPLE_REL(rel)); /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, we will bubble up - * the indexes of partitioned relations that appear down in the tree, so - * that when we've created Paths for all the children, the root - * partitioned table's list will contain all such indexes. - */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - rel->partitioned_child_rels = list_make1_int(rti); - - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - - /* * If this is a partitioned baserel, set the consider_partitionwise_join * flag; currently, we only consider partitionwise joins with the baserel * if its targetlist doesn't contain a whole-row Var. @@ -1392,12 +1232,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; ListCell *parentvars; ListCell *childvars; - ListCell *lc; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1405,142 +1241,22 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); + Assert(childrel != NULL); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); + /* + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals. + */ + if (IS_DUMMY_REL(childrel)) continue; - } - - /* - * We have to copy the parent's targetlist and quals to the child, - * with appropriate substitution of variables. However, only the - * baserestrictinfo quals are needed before we can check for - * constraint exclusion; so do that first and then check to see if we - * can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - continue; - } if (relation_excluded_by_constraints(root, childrel, childRTE)) { @@ -1553,7 +1269,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, } /* - * CE failed, so finish copying/modifying targetlist and join quals. + * Copy/Modify targetlist. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -3090,6 +2806,7 @@ inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) continue; childrel = find_base_rel(root, appinfo->child_relid); + Assert(childrel != NULL); /* Ignore excluded/pruned children. */ if (IS_DUMMY_REL(childrel)) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 663130461a..bc89de9a9d 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -20,6 +20,7 @@ #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" +#include "optimizer/tlist.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -51,6 +52,9 @@ static SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, Relids left_relids, Relids right_relids); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + int partidx); /* @@ -1383,6 +1387,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (IS_SIMPLE_REL(rel1) && child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts); + if (IS_SIMPLE_REL(rel1) && child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts); + /* * If a child table has consider_partitionwise_join=false, it means * that it's a dummy relation for which we skipped setting up tlist @@ -1674,3 +1683,55 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +static RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx) +{ + RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + AppendRelInfo *appinfo = make_append_rel_info(parent, parentrte, + parent->tupdesc, + parentrte->relid, + parent->reltype, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 1c78852a88..ebbdfdd40c 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -616,64 +616,6 @@ create_lateral_join_info(PlannerInfo *root) bms_add_member(brel2->lateral_referencers, rti); } } - - /* - * Lastly, propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - */ - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - RangeTblEntry *brte = root->simple_rte_array[rti]; - - /* - * Skip empty slots. Also skip non-simple relations i.e. dead - * relations. - */ - if (brel == NULL || !IS_SIMPLE_REL(brel)) - continue; - - /* - * In the case of table inheritance, the parent RTE is directly linked - * to every child table via an AppendRelInfo. In the case of table - * partitioning, the inheritance hierarchy is expanded one level at a - * time rather than flattened. Therefore, an other member rel that is - * a partitioned table may have children of its own, and must - * therefore be marked with the appropriate lateral info so that those - * children eventually get marked also. - */ - Assert(brte); - if (brel->reloptkind == RELOPT_OTHER_MEMBER_REL && - (brte->rtekind != RTE_RELATION || - brte->relkind != RELKIND_PARTITIONED_TABLE)) - continue; - - if (brte->inh) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); - RelOptInfo *childrel; - - if (appinfo->parent_relid != rti) - continue; - childrel = root->simple_rel_array[appinfo->child_relid]; - Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - Assert(childrel->direct_lateral_relids == NULL); - childrel->direct_lateral_relids = brel->direct_lateral_relids; - Assert(childrel->lateral_relids == NULL); - childrel->lateral_relids = brel->lateral_relids; - Assert(childrel->lateral_referencers == NULL); - childrel->lateral_referencers = brel->lateral_referencers; - } - } - } } diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index e536828e2c..44507e029c 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -281,6 +281,16 @@ query_planner(PlannerInfo *root, List *tlist, } /* + * Expand RT entries that represent inherited or partitioned tables. + * This will perform partition pruning on partitioned tables in the + * original range table and also recursively on any child partitioned + * tables that were added by the expansion of the original parent(s). + * As new entries are added to the range table, various arrays in the + * PlannerInfo will be expanded accordingly. + */ + expand_inherited_tables(root); + + /* * Add child subroots needed to use during planning for individual child * targets */ diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 7c53785c55..3269d082d8 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -25,6 +25,7 @@ #include "access/table.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -717,27 +718,18 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } /* - * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). - */ - preprocess_rowmarks(root); - - /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain RTE_RELATION entries, so it's OK (and marginally more efficient) - * to do it after checking for joins and other special RTEs. We must do - * this after pulling up subqueries, else we'd fail to handle inherited - * tables in subqueries. - */ - expand_inherited_tables(root); - - /* * Now that we have figured out "actual" inheritance situation of the * relations, set whether the query is an inherited UPDATE/DELETE. */ @@ -745,6 +737,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh); /* + * Preprocess RowMark information. We need to do this after subquery + * pullup (so that all non-inherited RTEs are present). + */ + preprocess_rowmarks(root); + + /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -1254,7 +1252,9 @@ inheritance_planner(PlannerInfo *root) * of the parent. */ root->unexpanded_tlist = list_copy(root->parse->targetList); - tlist = preprocess_targetlist(root); + + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root, false); root->processed_tlist = tlist; qp_extra.tlist = tlist; qp_extra.activeWindows = NIL; @@ -1264,9 +1264,11 @@ inheritance_planner(PlannerInfo *root) /* * If it turned out during query planning that all the children are dummy * (pruned or excluded by constraints), no need to do the steps below. - * Let grouping_planner finish up the final path. + * For regular inheritance case, it's possible that we only need to modify + * the parent table. Let grouping_planner finish up the final path. */ - if (IS_DUMMY_REL(planned_rel)) + parent_rte = planner_rt_fetch(top_parentRTindex, root); + if (!parent_rte->inh || IS_DUMMY_REL(planned_rel)) { grouping_planner(root, false, planned_rel, 0.0); return; @@ -1282,7 +1284,6 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = planner_rt_fetch(top_parentRTindex, root); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1611,14 +1612,19 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, */ if (planned_rel == NULL) { - tlist = preprocess_targetlist(root); + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root, false); /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation - * so that we can transfer its decoration (resnames etc) to the - * topmost tlist of the finished Plan. + * We are now mostly done hacking up the query's targetlist. More + * columns might be added during query_planner after inheritance + * expansion in make_one_rel, because some children may require + * different column than the parent for row mark handling; see + * add_rowmark_junk_columns(). Most of the remaining planning + * work will be done with the PathTarget representation of tlists, + * but save aside the full representation so that we can transfer + * its decoration (resnames etc) to the topmost tlist of the + * finished Plan. */ root->processed_tlist = tlist; } @@ -2394,7 +2400,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2419,7 +2425,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } @@ -6856,6 +6862,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -6957,6 +6967,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 6bd3b2d1ac..0287c7b284 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -325,6 +325,12 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) UPPERREL_FINAL, NULL))) add_rtes_to_flat_rtable(rel->subroot, true); } + /* + * A NULL rel also means an unplanned subquery rte, so apply + * flatten_unplanned_rtes. + */ + else + flatten_unplanned_rtes(glob, rte); } rti++; } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 0e045f171a..ccd4f13b83 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -65,9 +65,13 @@ static List *expand_targetlist(List *tlist, int command_type, * * As a side effect, if there's an ON CONFLICT UPDATE clause, its targetlist * is also preprocessed (and updated in-place). + * + * inheritance_expanded specifies whether inheritance tables are expanded + * as of calling this function. Caller must specify it because that affects + * which row marking related junk columns get added to the targetlist. */ List * -preprocess_targetlist(PlannerInfo *root) +preprocess_targetlist(PlannerInfo *root, bool inheritance_expanded) { Query *parse = root->parse; int result_relation = parse->resultRelation; @@ -134,6 +138,15 @@ preprocess_targetlist(PlannerInfo *root) if (rc->rti != rc->prti) continue; + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which will change the parent row + * mark's allMarkTypes fields. + */ + if (rc->isParent && !inheritance_expanded) + continue; + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) { /* Need to fetch TID */ diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 55eeb5127c..d4d36248a1 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -31,13 +31,16 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" +#include "optimizer/var.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" #include "utils/lsyscache.h" diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index 34a3ebbbe4..d2c3c7b590 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -31,10 +31,10 @@ typedef struct AppendRelInfo **appinfos; } adjust_appendrel_attrs_context; -static void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, adjust_appendrel_attrs_context *context); static List *adjust_inherited_tlist(List *tlist, @@ -46,18 +46,20 @@ static List *adjust_inherited_tlist(List *tlist, * Build an AppendRelInfo for the parent-child pair */ AppendRelInfo * -make_append_rel_info(Relation parentrel, Relation childrel, - Index parentRTindex, Index childRTindex) +make_append_rel_info(RelOptInfo *parent, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childtype, + Index childRTindex) { AppendRelInfo *appinfo = makeNode(AppendRelInfo); - appinfo->parent_relid = parentRTindex; + appinfo->parent_relid = parent->relid; appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->rd_rel->reltype; - appinfo->child_reltype = childrel->rd_rel->reltype; - make_inh_translation_list(parentrel, childrel, childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = RelationGetRelid(parentrel); + appinfo->parent_reltype = parent->reltype; + appinfo->child_reltype = childtype; + make_inh_translation_list(parent->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; return appinfo; } @@ -70,14 +72,11 @@ make_append_rel_info(Relation parentrel, Relation childrel, * For paranoia's sake, we match type/collation as well as attribute name. */ static void -make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, - List **translated_vars) +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) { List *vars = NIL; - TupleDesc old_tupdesc = RelationGetDescr(oldrelation); - TupleDesc new_tupdesc = RelationGetDescr(newrelation); - Oid new_relid = RelationGetRelid(newrelation); int oldnatts = old_tupdesc->natts; int newnatts = new_tupdesc->natts; int old_attno; @@ -107,7 +106,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, * When we are generating the "translation list" for the parent table * of an inheritance set, no need to search for matches. */ - if (oldrelation == newrelation) + if (from_rel == to_rel) { vars = lappend(vars, makeVar(newvarno, (AttrNumber) (old_attno + 1), @@ -133,10 +132,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, { HeapTuple newtup; - newtup = SearchSysCacheAttName(new_relid, attname); + newtup = SearchSysCacheAttName(to_rel, attname); if (!newtup) elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; ReleaseSysCache(newtup); @@ -146,10 +145,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, /* Found it, check type and collation match */ if (atttypid != att->atttypid || atttypmod != att->atttypmod) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); if (attcollation != att->attcollation) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); vars = lappend(vars, makeVar(newvarno, (AttrNumber) (new_attno + 1), diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 56b1870a6f..89f6ee2bc2 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -18,33 +18,43 @@ #include "access/table.h" #include "catalog/partition.h" #include "catalog/pg_inherits.h" +#include "catalog/pg_type.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" #include "optimizer/inherit.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" +#include "optimizer/var.h" +#include "partitioning/partprune.h" #include "utils/rel.h" static PlannerInfo *create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo); -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); +static void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti); +static void expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, + RangeTblEntry *rte, Index rti, RelOptInfo *rel); static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); - +static RelOptInfo *build_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); /* * expand_inherited_tables @@ -52,37 +62,145 @@ static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, * into an "append relation". At the conclusion of this process, * the "inh" flag is set in all and only those RTEs that are append * relation parents. + * + * Note that although we're calling the combined output of inheritance set + * relations an "append relation" here, the caller may not always want to + * combine the relations. For example, if the parent of the inheritance + * set is the query's target relation, each child relation is processed + * on its own as the query's target relation. */ void expand_inherited_tables(PlannerInfo *root) { - Index nrtes; + int orig_rtable_size; Index rti; - ListCell *rl; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; /* * expand_inherited_rtentry may add RTEs to parse->rtable. The function is * expected to recursively handle any RTEs that it creates with inh=true. * So just scan as far as the original end of the rtable list. */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) + for (rti = 1; rti < orig_rtable_size; rti++) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + if (rte->inh) + expand_inherited_rtentry(root, brel, rte, rti); } } /* * expand_inherited_rtentry - * Check whether a rangetable entry represents an inheritance set. - * If so, add entries for all the child tables to the query's - * rangetable, and build AppendRelInfo nodes for all the child tables - * and add them to root->append_rel_list. If not, clear the entry's - * "inh" flag to prevent later code from looking for AppendRelInfos. + * This initializes RelOptInfos for inheritance child relations if the + * passed-in relation has any + * + * 'rel' is the parent relation, whose range table entry ('rte') has been + * marked to require adding children. Parent could either be a subquery (if + * we flattened UNION ALL query) or a table that's known to have (or once had) + * inheritance children. The latter consists of both regular inheritance + * parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as a child to be scanned as part of the + * inheritance set. The original RTE is considered to represent the whole + * inheritance set. + */ +static void +expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_inheritance_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_nonpartitioned_inherited_rtentry(root, rte, rti, rel); + } +} + +/* + * expand_nonpartitioned_inherited_rtentry + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. * * Note that the original RTE is considered to represent the whole * inheritance set. The first of the generated RTEs is an RTE for the same @@ -93,55 +211,35 @@ expand_inherited_tables(PlannerInfo *root) * regular inheritance, a parent RTE must always have at least two associated * AppendRelInfos: one corresponding to the parent table as a simple member of * inheritance set and one or more corresponding to the actual children. - * Since a partitioned table is not scanned, it might have only one associated - * AppendRelInfo. */ static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, + RangeTblEntry *rte, + Index rti, + RelOptInfo *rel) { Oid parentOID; PlanRowMark *oldrc; - Relation oldrelation; - LOCKMODE lockmode; + LOCKMODE lockmode = rte->rellockmode; List *inhOIDs; ListCell *l; + int num_children; + int num_children_added = 0; - /* Does RT entry allow inheritance? */ - if (!rte->inh) - return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } - /* Fast path for common case of childless table */ + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); parentOID = rte->relid; - if (!has_subclass(parentOID)) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * The rewriter should already have obtained an appropriate lock on each - * relation named in the query. However, for each child relation we add - * to the query, we must obtain an appropriate lock, because this will be - * the first use of those relations in the parse/rewrite/plan pipeline. - * Child rels should use the same lockmode as their parent. - */ - lockmode = rte->rellockmode; /* Scan for all members of inheritance set, acquire needed locks */ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); /* * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite above has_subclass() check, if table - * once had a child but no longer does. + * case. This could happen despite has_subclass() check performed by + * subquery_planner, if table once had a child but no longer does. */ - if (list_length(inhOIDs) < 2) + num_children = list_length(inhOIDs); + if (num_children < 2) { /* Clear flag before returning */ rte->inh = false; @@ -149,216 +247,221 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } /* - * If parent relation is selected FOR UPDATE/SHARE, we need to mark its - * PlanRowMark as isParent = true, and generate a new PlanRowMark for each - * child. + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. */ oldrc = get_plan_rowmark(root->rowMarks, rti); - if (oldrc) - oldrc->isParent = true; + Assert(oldrc == NULL || oldrc->isParent); /* - * Must open the parent relation to examine its tupdesc. We need not lock - * it; we assume the rewriter already did. + * Must expand PlannerInfo arrays by num_children before we can add + * children. */ - oldrelation = table_open(parentOID, NoLock); + Assert(num_children > 0); + expand_planner_arrays(root, num_children); - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) + foreach(l, inhOIDs) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); - - /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. - */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); - } - else - { - List *appinfos = NIL; + Oid childOID = lfirst_oid(l); + Relation newrelation; RangeTblEntry *childrte; Index childRTindex; + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. */ - foreach(l, inhOIDs) + if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = table_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - table_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - table_close(newrelation, NoLock); + table_close(newrelation, lockmode); + continue; } - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); + (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation, &childrte, + &childRTindex); + Assert(childrte != NULL); + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + Assert(childRTindex > 0); + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; } - table_close(oldrelation, NoLock); + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } } /* * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) + Index parentRTindex, RelOptInfo *parentrel) { + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; - check_stack_depth(); - - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + if (bms_num_members(partindexes) > 0) + expand_planner_arrays(root, bms_num_members(partindexes)); /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. */ - if (partdesc->nparts == 0) - { - parentrte->inh = false; - return; - } + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); - for (i = 0; i < partdesc->nparts; i++) + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) { - Oid childOID = partdesc->oids[i]; - Relation childrel; + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + RangeTblEntry *childrte; + Index childRTindex; /* Open rel; we already have required locks */ - childrel = table_open(childOID, NoLock); + newrelation = table_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation, + &childrte, &childRTindex); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + /* Close child relations, but keep locks */ + table_close(newrelation, NoLock); + + /* If the child is partitioned itself, expand it too. */ + if (childrel->part_scheme) + { + Assert(childrte->inh); expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); + childrel); + } + } - /* Close child relation, but keep locks */ - table_close(childrel, NoLock); + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); } } /* - * expand_single_inheritance_child - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) + * The return value is the RelOptInfo that's added. * * PlanRowMarks still carry the top-parent's RTI, and the top-parent's * allMarkTypes field still accumulates values from all descendents. * * "parentrte" and "parentRTindex" are immediate parent's RTE and * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. */ -static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); Oid childOID = RelationGetRelid(childrel); RangeTblEntry *childrte; Index childRTindex; AppendRelInfo *appinfo; + RelOptInfo *childrelopt; /* * Build an RTE for the child, and attach to query's rangetable list. We * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh = false. Also, set requiredPerms to zero since - * all required permissions checks are done on the original RTE. Likewise, - * set the child's securityQuals to empty, because we only want to apply - * the parent's RLS conditions regardless of what RLS properties + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties * individual children may have. (This is an intentional choice to make * inherited RLS work like regular permissions checks.) The parent * securityQuals will be propagated to children along with other base @@ -368,49 +471,42 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childrte_p = childrte; childrte->relid = childOID; childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) - childrte->inh = true; - else - childrte->inh = false; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); childrte->requiredPerms = 0; childrte->securityQuals = NIL; parse->rtable = lappend(parse->rtable, childrte); childRTindex = list_length(parse->rtable); *childRTindex_p = childRTindex; - /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. - */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) - { - appinfo = make_append_rel_info(parentrel, childrel, - parentRTindex, childRTindex); - *appinfos = lappend(*appinfos, appinfo); + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + RelationGetDescr(childrel), + RelationGetRelid(childrel), + RelationGetForm(childrel)->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); } /* @@ -442,6 +538,253 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need to create one, + * because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_inheritance_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; +} + +/* + * build_inheritance_child_rel + * Build a RelOptInfo for child relation of an inheritance set + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields based on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turn out to be self-contradictory, + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + List *childquals; + ListCell *lc; + bool have_const_false_cq; + Index cq_min_security; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + * + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities + * for const-simplification, and perhaps even pseudoconstant quals. + * Therefore, transform each RestrictInfo separately to see if it + * reduces to a constant or pseudoconstant. (We must process them + * separately to keep track of the security level of each qual.) + */ + childquals = false; + cq_min_security = UINT_MAX; + have_const_false_cq = false; + foreach(lc, parent->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual = (Node *) rinfo->clause; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, childqual, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + have_const_false_cq = true; + break; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might + * have securityQuals associated with this particular child node. + * (Currently this can only happen in appendrels originating from + * UNION ALL; inheritance child tables don't have their own + * securityQuals.) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except + * that we can't make any general deductions from such quals, + * since they don't hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* + * not likely that we'd see constants here, so no + * check + */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, + false, + false, + security_level, + NULL, NULL, + NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* Set child's version of baserestrictinfo. */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + if (have_const_false_cq) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; } /* @@ -584,7 +927,7 @@ create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) * column, filling placeholder entries for dropped columns, etc., all of * which occurs with the child's TupleDesc. */ - tlist = preprocess_targetlist(subroot); + tlist = preprocess_targetlist(subroot, true); subroot->processed_tlist = tlist; /* Add any newly added Vars to the child RelOptInfo. */ diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 9aa819c002..f21120c4e8 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -105,20 +105,20 @@ static void set_baserel_partition_key_exprs(Relation relation, * important for it. */ void -get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) +get_relation_info(PlannerInfo *root, RangeTblEntry *rte, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; bool hasindex; List *indexinfos = NIL; + bool inhparent = rte->inh; /* * We need not lock the relation since it was already locked, either by * the rewriter or when expand_inherited_rtentry() added it to the query's * rangetable. */ - relation = table_open(relationObjectId, NoLock); + relation = heap_open(rte->relid, NoLock); /* Temporary and unlogged relations are inaccessible during recovery. */ if (!RelationNeedsWAL(relation) && RecoveryInProgress()) @@ -444,11 +444,32 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, get_relation_foreign_keys(root, rel, relation, inhparent); /* - * Collect info about relation's partitioning scheme, if any. Only - * inheritance parents may be partitioned. + * Collect some additional information for inheritance parents. */ - if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - set_relation_partition_info(root, rel, relation); + if (inhparent) + { + /* + * We'll need the TupleDesc when initializing the child relation. + * A copy is being made because concurrent changes might drop + * the relcache entry. That's possible because ALTER TABLE + * child_table NO INHERIT parent_table only requires an + * AccessShareLock on parent_table. + */ + rel->tupdesc = CreateTupleDescCopy(RelationGetDescr(relation)); + rel->reltype = RelationGetForm(relation)->reltype; + + /* + * If partitioned, also save the information of partitioning scheme, + * and whether the query updates any of the partition key columns. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + set_relation_partition_info(root, rel, relation); + root->partColsUpdated |= has_partition_attrs(relation, + rte->updatedCols, + NULL); + } + } table_close(relation, NoLock); @@ -458,7 +479,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * removing an index, or adding a hypothetical index to the indexlist. */ if (get_relation_info_hook) - (*get_relation_info_hook) (root, relationObjectId, inhparent, rel); + (*get_relation_info_hook) (root, rte->relid, rte->inh, rel); } /* @@ -1845,16 +1866,20 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; + + /* + * Since we must've taken a lock on the table, it's okay to simply copy + * the pointers to relcache data here. + */ + rel->part_oids = partdesc->oids; + rel->boundinfo = partdesc->boundinfo; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index f04c6b76f4..09b1b8075a 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -132,6 +132,50 @@ setup_append_rel_array(PlannerInfo *root) } /* + * expand_planner_arrays + * Expand the PlannerInfo arrays by add_size members and initialize the + * the newly added bytes with zero + */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size; + + Assert(add_size > 0); + + new_size = root->simple_rel_array_size + add_size; + + /* Expand various arrays and 0-initialize added bytes. */ + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + + if (root->append_rel_array) + { + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + } + else + { + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + } + + root->simple_rel_array_size = new_size; +} + +/* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. */ @@ -195,6 +239,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->joininfo = NIL; rel->has_eclass_joins = false; rel->consider_partitionwise_join = false; /* might get changed later */ + rel->top_parent_relids = NULL; /* might be changed later */ rel->part_scheme = NULL; rel->nparts = 0; rel->boundinfo = NULL; @@ -203,6 +248,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->partexprs = NULL; rel->nullable_partexprs = NULL; rel->partitioned_child_rels = NIL; + rel->inh_root_parent = 0; /* might be changed later */ /* * Pass top parent's relids down the inheritance hierarchy. If the parent @@ -216,16 +262,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->top_parent_relids = parent->top_parent_relids; else rel->top_parent_relids = bms_copy(parent->relids); + + /* + * For inheritance child relations, we also set inh_root_parent. + * Note that 'parent' might itself be a child (a sub-partitioned + * partition), in which case we simply use its value of + * inh_root_parent. + */ + if (parent->rtekind == RTE_RELATION) + rel->inh_root_parent = parent->inh_root_parent > 0 ? + parent->inh_root_parent : + parent->relid; } - else - rel->top_parent_relids = NULL; /* Check type of rtable entry */ switch (rte->rtekind) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte, rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -273,52 +328,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); - /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. - */ - if (rte->inh) - { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); - - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; - } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); - } - return rel; } diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 901433c68c..9bb472fd98 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -46,6 +46,7 @@ #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" #include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/planner.h" #include "optimizer/predtest.h" @@ -437,17 +438,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -541,23 +548,20 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ @@ -565,6 +569,13 @@ prune_append_rel_partitions(RelOptInfo *rel) return NULL; /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. */ @@ -591,15 +602,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index c208fe1533..178b86c373 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -15,6 +15,7 @@ #define RELATION_H #include "access/sdir.h" +#include "access/tupdesc.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/params.h" @@ -729,11 +730,28 @@ typedef struct RelOptInfo int nparts; /* number of partitions */ struct PartitionBoundInfoData *boundinfo; /* Partition bounds */ List *partition_qual; /* partition constraint */ + Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ + + /* + * For inheritance children, this is the RT index of inheritance table + * mentioned in the query from which this relation originated. + * top_parent_relids cannot be used for this, because if the inheritance + * root table is itself under UNION ALL, top_parent_relids contains the + * RT index of UNION ALL parent subquery. + */ + Index inh_root_parent; + + /* + * Set only if this is an inheritance parent relation. This information + * is needed when initializing the planning info for children. + */ + TupleDesc tupdesc; /* A "copy" of the table's tuple desriptor */ + Oid reltype; /* Table's reltype */ } RelOptInfo; /* diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h index 604e36d73c..8f7b06a608 100644 --- a/src/include/optimizer/appendinfo.h +++ b/src/include/optimizer/appendinfo.h @@ -17,9 +17,10 @@ #include "nodes/relation.h" #include "utils/relcache.h" -extern AppendRelInfo *make_append_rel_info(Relation parentrel, - Relation childrel, - Index parentRTindex, Index childRTindex); +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parent, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childtype, + Index childRTindex); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index aaaf3f4ff5..ba0f60d089 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -265,6 +265,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index a1b23251a1..d8091cdfa4 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -25,8 +25,8 @@ typedef void (*get_relation_info_hook_type) (PlannerInfo *root, extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; -extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); +extern void get_relation_info(PlannerInfo *root, RangeTblEntry *rte, + RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index a03a024ce9..2cbb508a4c 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -40,7 +40,8 @@ extern Expr *canonicalize_qual(Expr *qual, bool is_check); /* * prototypes for preptlist.c */ -extern List *preprocess_targetlist(PlannerInfo *root); +extern List *preprocess_targetlist(PlannerInfo *root, + bool inheritance_expanded); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); diff --git a/src/include/partitioning/partprune.h b/src/include/partitioning/partprune.h index ce9bc8d9fd..5c5d27d040 100644 --- a/src/include/partitioning/partprune.h +++ b/src/include/partitioning/partprune.h @@ -76,7 +76,7 @@ extern PartitionPruneInfo *make_partition_pruneinfo(PlannerInfo *root, List *subpaths, List *partitioned_rels, List *prunequal); -extern Relids prune_append_rel_partitions(RelOptInfo *rel); +extern Bitmapset *prune_append_rel_partitions(RelOptInfo *rel); extern Bitmapset *get_matching_partitions(PartitionPruneContext *context, List *pruning_steps); diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 6bc106831e..1450cef057 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) -- 2.11.0
v18-0003-Teach-planner-to-only-process-unpruned-partition.patch (text/plain, 6.8K) download From b2b37def9669be4f148f8ea1912a16f965712c31 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 15 Oct 2018 10:59:24 +0900 Subject: [PATCH v18 3/4] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/inherit.c | 1 + src/backend/optimizer/util/relnode.c | 3 +++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/relation.h | 4 ++++ 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index bc89de9a9d..3d5b2df8d8 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1451,6 +1451,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 3269d082d8..40caf84f65 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6854,7 +6854,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -6862,9 +6864,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -6945,7 +6945,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -6957,7 +6956,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -6967,9 +6968,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 89f6ee2bc2..2e892190e1 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -361,6 +361,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, /* Perform pruning. */ partindexes = prune_append_rel_partitions(parentrel); + parentrel->live_parts = partindexes; /* Must expand PlannerInfo arrays before we can add children. */ if (bms_num_members(partindexes) > 0) diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 09b1b8075a..cfb8077575 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1734,6 +1734,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 9bb472fd98..088b191bdd 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -441,29 +441,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 178b86c373..74293acb04 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -733,6 +733,10 @@ typedef struct RelOptInfo Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Indexes into part_rels of the non-NULL + * RelOptInfos of unpruned partitions; exists + * to avoid having to iterate over the entire + * part_rels array to filter NULL entries. */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.11.0
v18-0004-Do-not-lock-all-partitions-at-the-beginning.patch (text/plain, 1.9K) download From a591475fd8254dce8fe49e6bf83e78dd6b73d1d6 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 12 Oct 2018 19:12:53 +0900 Subject: [PATCH v18 4/4] Do not lock all partitions at the beginning --- src/backend/optimizer/util/inherit.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 2e892190e1..e1c54000aa 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -328,10 +328,6 @@ expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, * * Partitions are added to the query in order in which they are found in * the parent's PartitionDesc. - * - * Note: even though only the unpruned partitions will be added to the - * resulting plan, this still locks *all* partitions via find_all_inheritors - * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, @@ -345,10 +341,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, parentrel->inh_root_parent : parentRTindex; - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - /* * Initialize partitioned_child_rels to contain this RT index. * @@ -385,8 +377,11 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, RangeTblEntry *childrte; Index childRTindex; - /* Open rel; we already have required locks */ - newrelation = table_open(childOID, NoLock); + /* + * Open rel; this's the first time of opening partitions for this + * query, so take the appropriate locks. + */ + newrelation = table_open(childOID, lockmode); Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-29 10:11  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  0 siblings, 1 reply; 265+ messages in thread

From: Amit Langote @ 2019-01-29 10:11 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Rowley <david.rowley@2ndquadrant.com>; Amit Langote <amitlangote09@gmail.com>; pgsql-hackers

Imai-san,

On 2019/01/28 10:44, Imai, Yoshikazu wrote:
> On Thu, Jan 24, 2019 at 6:10 AM, Imai, Yoshikazu wrote:
>> updating partkey case:
>>
>> part-num  master     0001     0002     0003     0004
>> 1        8215.34  7924.99  7931.15  8407.40  8475.65
>> 2        7137.49  7026.45  7128.84  7583.08  7593.73
>> 4        5880.54  5896.47  6014.82  6405.33  6398.71
>> 8        4222.96  4446.40  4518.54  4802.43  4785.82
>> 16       2634.91  2891.51  2946.99  3085.81  3087.91
>> 32        935.12  1125.28  1169.17  1199.44  1202.04
>> 64        352.37   405.27   417.09   425.78   424.53
>> 128       236.26   310.01   307.70   315.29   312.81
>> 256        65.36    86.84    87.67    84.39    89.27
>> 512        18.34    24.84    23.55    23.91    23.91
>> 1024        4.83     6.93     6.51     6.45     6.49
> 
> I also tested with non-partitioned table case.
> 
> updating partkey case:
> 
> part-num  master     0001     0002     0003     0004
> 0        10956.7  10370.5  10472.6  10571.0  10581.5
> 1        8215.34  7924.99  7931.15  8407.40  8475.65 
> ...
> 1024        4.83     6.93     6.51     6.45     6.49
> 
> 
> In my performance results, it seems update performance degrades in non-partitioned case with v17-patch applied.
> But it seems this degrades did not happen at v2-patch.
> 
> On Thu, Aug 30, 2018 at 1:45 AM, Amit, Langote wrote:
>> UPDATE:
>>
>> nparts  master    0001    0002   0003
>> ======  ======    ====    ====   ====
>> 0         2856    2893    2862   2816
> 
> Does this degradation only occur in my tests? Or if this result is correct, what may causes the degradation?

I re-ran tests with v18 using the following setup [1]:

create table ht (a int, b int) partition by hash (b);
create table ht_0 partition of ht for values with (modulus N, remainder 0);
...
$ cat update-noprune.sql
update ht set a = 0;

pgbench -n -T 60 -f update-noprune,sql

TPS:

nparts  master    0001    0002   0003   0004
======  ======    ====    ====   ====   ====
0       4408      4335    4423   4379   4314
1       3883      3873    3679   3856   4007
2       3495      3476    3477   3500   3627

I can see some degradation for small number of partitions, but maybe it's
just noise?  At least, I don't yet have a systematic explanation for that
happening.

Thanks,
Amit

[1] I changed the compile flags in build scripts to drop
-DCATCACHE_FORCE_RELEASE, which would cause many syscache misses in my
test runs





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-30 02:20  David Rowley <david.rowley@2ndquadrant.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  2 siblings, 1 reply; 265+ messages in thread

From: David Rowley @ 2019-01-30 02:20 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On Tue, 29 Jan 2019 at 22:32, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
>
> On 2019/01/29 11:23, David Rowley wrote:
> > 7. In set_inherit_target_rel_sizes() I still don't really like the way
> > you're adjusting the EquivalenceClasses. Would it not be better to
> > invent a function similar to adjust_appendrel_attrs(), or just use
> > that function?
>
> OK, I added a function copy_eq_classes_for_child_root() that simply makes
> a copy of eq_classes from the source root (deep-copying where applicable)
> and returns the list.

hmm, but you've added a case for SpecialJoinInfo, is there a good
reason not just to do the translation by just adding an
EquivalenceClass case to adjust_appendrel_attrs_mutator() then just
get rid of the new "replace" flag in add_child_rel_equivalences()?
That way you'd also remove the churn in the couple of places you've
had to modify the existing calls to add_child_rel_equivalences().

-- 
 David Rowley                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-01-30 03:11  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 1 reply; 265+ messages in thread

From: Imai, Yoshikazu @ 2019-01-30 03:11 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Rowley <david.rowley@2ndquadrant.com>; Amit Langote <amitlangote09@gmail.com>; pgsql-hackers

Amit-san,

On Tue, Jan 29, 2019 at 10:11 AM, Amit Langote wrote:
> On 2019/01/28 10:44, Imai, Yoshikazu wrote:
> > On Thu, Jan 24, 2019 at 6:10 AM, Imai, Yoshikazu wrote:
> >> updating partkey case:
> >>
> >> part-num  master     0001     0002     0003     0004
> >> 1        8215.34  7924.99  7931.15  8407.40  8475.65
> >> 2        7137.49  7026.45  7128.84  7583.08  7593.73
> >> 4        5880.54  5896.47  6014.82  6405.33  6398.71
> >> 8        4222.96  4446.40  4518.54  4802.43  4785.82
> >> 16       2634.91  2891.51  2946.99  3085.81  3087.91
> >> 32        935.12  1125.28  1169.17  1199.44  1202.04
> >> 64        352.37   405.27   417.09   425.78   424.53
> >> 128       236.26   310.01   307.70   315.29   312.81
> >> 256        65.36    86.84    87.67    84.39    89.27
> >> 512        18.34    24.84    23.55    23.91    23.91
> >> 1024        4.83     6.93     6.51     6.45     6.49
> >
> > I also tested with non-partitioned table case.
> >
> > updating partkey case:
> >
> > part-num  master     0001     0002     0003     0004
> > 0        10956.7  10370.5  10472.6  10571.0  10581.5
> > 1        8215.34  7924.99  7931.15  8407.40  8475.65
> > ...
> > 1024        4.83     6.93     6.51     6.45     6.49
> >
> >
> > In my performance results, it seems update performance degrades in
> non-partitioned case with v17-patch applied.
> > But it seems this degrades did not happen at v2-patch.
> >
> > On Thu, Aug 30, 2018 at 1:45 AM, Amit, Langote wrote:
> >> UPDATE:
> >>
> >> nparts  master    0001    0002   0003
> >> ======  ======    ====    ====   ====
> >> 0         2856    2893    2862   2816
> >
> > Does this degradation only occur in my tests? Or if this result is correct,
> what may causes the degradation?
> 
> I re-ran tests with v18 using the following setup [1]:
> 
> create table ht (a int, b int) partition by hash (b); create table ht_0
> partition of ht for values with (modulus N, remainder 0); ...
> $ cat update-noprune.sql
> update ht set a = 0;
> 
> pgbench -n -T 60 -f update-noprune,sql
> 
> TPS:
> 
> nparts  master    0001    0002   0003   0004
> ======  ======    ====    ====   ====   ====
> 0       4408      4335    4423   4379   4314
> 1       3883      3873    3679   3856   4007
> 2       3495      3476    3477   3500   3627
> 
> I can see some degradation for small number of partitions, but maybe it's
> just noise?  At least, I don't yet have a systematic explanation for that
> happening.

Thanks for testing.

I also re-ran tests with v18 using settings almost same as I used before, but this time I run pgbench for 60 second which was 30 second in previous test.

TPS:

 nparts  master    0001    0002   0003   0004
 ======  ======    ====    ====   ====   ====
 0       10794     11018   10761  10552  11066
 1       7574      7625    7558   8071   8219
 2       6745      6778    6746   7281   7344

I can see no degradation, so I also think that performance degradation in my previous test and your test was because of just noise.


Why I did these tests is that I wanted to confirm that even if we apply each patch one by one, there's no performance problem. Because patches are quite large, I just felt it might be difficult to commit these patches all at once and I thought committing patch one by one would be another option to commit these patches. I don't know there is the rule in the community how patches should be committed, and if there, my thoughts above may be bad.

Anyway, I'll restart code reviewing :)

--
Yoshikazu Imai


^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-30 03:25  Alvaro Herrera <alvherre@2ndquadrant.com>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  0 siblings, 1 reply; 265+ messages in thread

From: Alvaro Herrera @ 2019-01-30 03:25 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; David Rowley <david.rowley@2ndquadrant.com>; Amit Langote <amitlangote09@gmail.com>; pgsql-hackers

On 2019-Jan-30, Imai, Yoshikazu wrote:

> Why I did these tests is that I wanted to confirm that even if we
> apply each patch one by one, there's no performance problem. Because
> patches are quite large, I just felt it might be difficult to commit
> these patches all at once and I thought committing patch one by one
> would be another option to commit these patches. I don't know there is
> the rule in the community how patches should be committed, and if
> there, my thoughts above may be bad.

There are no absolute rules, but if I was committing it, I would
certainly commit each separately, mostly because reviewing the whole
series at once looks daunting ... and given the proposed commit
messages, I'd guess that writing a combined commit message would also be
very difficult.

So thanks for doing these tests.

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-01-30 04:23  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Alvaro Herrera <alvherre@2ndquadrant.com>
  0 siblings, 0 replies; 265+ messages in thread

From: Imai, Yoshikazu @ 2019-01-30 04:23 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; David Rowley <david.rowley@2ndquadrant.com>; Amit Langote <amitlangote09@gmail.com>; pgsql-hackers

On Wed, Jan 30, 2019 at 3:26 PM, Alvaro Herrera wrote:
> On 2019-Jan-30, Imai, Yoshikazu wrote:
> 
> > Why I did these tests is that I wanted to confirm that even if we
> > apply each patch one by one, there's no performance problem. Because
> > patches are quite large, I just felt it might be difficult to commit
> > these patches all at once and I thought committing patch one by one
> > would be another option to commit these patches. I don't know there
> is
> > the rule in the community how patches should be committed, and if
> > there, my thoughts above may be bad.
> 
> There are no absolute rules, but if I was committing it, I would certainly
> commit each separately, mostly because reviewing the whole series at once
> looks daunting ... and given the proposed commit messages, I'd guess that
> writing a combined commit message would also be very difficult.

Ah, I see.
 
> So thanks for doing these tests.

I'm glad to hear that!


Thanks
--
Yoshikazu Imai



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-30 17:28  Amit Langote <amitlangote09@gmail.com>
  parent: David Rowley <david.rowley@2ndquadrant.com>
  0 siblings, 0 replies; 265+ messages in thread

From: Amit Langote @ 2019-01-30 17:28 UTC (permalink / raw)
  To: David Rowley <david.rowley@2ndquadrant.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On Wed, Jan 30, 2019 at 3:20 AM David Rowley
<david.rowley@2ndquadrant.com> wrote:
>
> On Tue, 29 Jan 2019 at 22:32, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
> >
> > On 2019/01/29 11:23, David Rowley wrote:
> > > 7. In set_inherit_target_rel_sizes() I still don't really like the way
> > > you're adjusting the EquivalenceClasses. Would it not be better to
> > > invent a function similar to adjust_appendrel_attrs(), or just use
> > > that function?
> >
> > OK, I added a function copy_eq_classes_for_child_root() that simply makes
> > a copy of eq_classes from the source root (deep-copying where applicable)
> > and returns the list.
>
> hmm, but you've added a case for SpecialJoinInfo, is there a good
> reason not just to do the translation by just adding an
> EquivalenceClass case to adjust_appendrel_attrs_mutator() then just
> get rid of the new "replace" flag in add_child_rel_equivalences()?
> That way you'd also remove the churn in the couple of places you've
> had to modify the existing calls to add_child_rel_equivalences().

Sounds like something worth trying to make work.  Will do, thanks for the idea.

Thanks,
Amit




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-01-30 19:32  David Rowley <david.rowley@2ndquadrant.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  2 siblings, 1 reply; 265+ messages in thread

From: David Rowley @ 2019-01-30 19:32 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On Tue, 29 Jan 2019 at 22:32, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> Attached updated patches.

I think we could make the 0001 patch a bit smaller if we were to apply
the attached first.

Details in the commit message.

What do you think?

-- 
 David Rowley                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

0001-Move-building-of-child-base-quals-out-into-a-new-fun.patch (application/octet-stream, 11.4K) download From 70cf7a1fec448d899a43d69a4971df79a9d8c129 Mon Sep 17 00:00:00 2001 From: "dgrowley@gmail.com" <dgrowley@gmail.com> Date: Thu, 31 Jan 2019 07:27:13 +1300 Subject: [PATCH] Move building of child base quals out into a new function An upcoming patch which changes how inheritance planning works requires adding a new function that does a similar job to set_append_rel_size() but for child target relations. To save it from having to duplicate the qual building code, here we move that to a separate function first. Here we also change things so that we never attempt to build security quals after detecting some const false child quals. We needlessly used to do this just before we marked the child relation as a dummy rel. In passing, this also moves the partition pruned check to before the qual building code. We don't need to build the child quals before we check if the partition has been pruned. --- src/backend/optimizer/path/allpaths.c | 255 ++++++++++++++++++---------------- 1 file changed, 137 insertions(+), 118 deletions(-) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 2144e14ec8..95045040d8 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -138,7 +138,9 @@ static void subquery_push_qual(Query *subquery, static void recurse_push_qual(Node *setOp, Query *topquery, RangeTblEntry *rte, Index rti, Node *qual); static void remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel); - +static bool apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *childrel, + RangeTblEntry *childRTE, AppendRelInfo *appinfo); /* * make_one_rel @@ -1010,12 +1012,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, int childRTindex; RangeTblEntry *childRTE; RelOptInfo *childrel; - List *childquals; - Index cq_min_security; - bool have_const_false_cq; ListCell *parentvars; ListCell *childvars; - ListCell *lc; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1031,119 +1029,21 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); + if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) + { + /* This partition was pruned; skip it. */ + set_dummy_rel_pathlist(childrel); + continue; + } + /* * We have to copy the parent's targetlist and quals to the child, * with appropriate substitution of variables. However, only the * baserestrictinfo quals are needed before we can check for * constraint exclusion; so do that first and then check to see if we * can disregard this child. - * - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities - * for const-simplification, and perhaps even pseudoconstant quals. - * Therefore, transform each RestrictInfo separately to see if it - * reduces to a constant or pseudoconstant. (We must process them - * separately to keep track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - have_const_false_cq = false; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - have_const_false_cq = true; - break; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. - * (Currently this can only happen in appendrels originating from - * UNION ALL; inheritance child tables don't have their own - * securityQuals, see expand_inherited_rtentry().) Pull any such - * securityQuals up into the baserestrictinfo for the child. This is - * similar to process_security_barrier_quals() for the parent rel, - * except that we can't make any general deductions from such quals, - * since they don't hold for the whole appendrel. */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - if (have_const_false_cq) + if (!apply_child_basequals(root, rel, childrel, childRTE, appinfo)) { /* * Some restriction clause reduced to constant FALSE or NULL after @@ -1153,13 +1053,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, continue; } - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - if (relation_excluded_by_constraints(root, childrel, childRTE)) { /* @@ -3660,6 +3553,132 @@ generate_partitionwise_join_paths(PlannerInfo *root, RelOptInfo *rel) list_free(live_children); } +/* + * apply_child_basequals + * Populate childrel's quals based on rel's quals translating them using + * appinfo. + * + * In UNION ALL cases, we may find that some quals, once translated evaulate + * to true, we can simply ignore such quals. When we find quals that evaluate + * to false or NULL, then we return false and don't apply any quals. In this + * case, we expect the caller to mark the relation as a dummy rel so that it + * is not scanned. + */ +static bool +apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *childrel, RangeTblEntry *childRTE, + AppendRelInfo *appinfo) +{ + List *childquals; + Index cq_min_security; + ListCell *lc; + + /* + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities for + * const-simplification, and perhaps even pseudoconstant quals. Therefore, + * transform each RestrictInfo separately to see if it reduces to a + * constant or pseudoconstant. (We must process them separately to keep + * track of the security level of each qual.) + */ + childquals = NIL; + cq_min_security = UINT_MAX; + foreach(lc, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, + (Node *) rinfo->clause, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + return false; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + /* track minimum security level among child quals */ + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might have + * securityQuals associated with this particular child node. (Currently + * this can only happen in appendrels originating from UNION ALL; + * inheritance child tables don't have their own securityQuals, see + * expand_inherited_rtentry().) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except that we + * can't make any general deductions from such quals, since they don't + * hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* not likely that we'd see constants here, so no check */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, false, false, + security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* + * OK, we've got all the baserestrictinfo quals for this child. + */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + return true; +} /***************************************************************************** * DEBUG SUPPORT -- 2.16.2.windows.1
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-01 09:51  Alvaro Herrera <alvherre@2ndquadrant.com>
  parent: David Rowley <david.rowley@2ndquadrant.com>
  0 siblings, 1 reply; 265+ messages in thread

From: Alvaro Herrera @ 2019-02-01 09:51 UTC (permalink / raw)
  To: David Rowley <david.rowley@2ndquadrant.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers

On 2019-Jan-31, David Rowley wrote:

> On Tue, 29 Jan 2019 at 22:32, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
> > Attached updated patches.
> 
> I think we could make the 0001 patch a bit smaller if we were to apply
> the attached first.
> 
> Details in the commit message.
> 
> What do you think?

Amit didn't say what he thinks, but I think it looks good and the
rationale for it makes sense, so I pushed it.  I only amended some
comments a little bit.

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-01 10:01  Alvaro Herrera <alvherre@2ndquadrant.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  2 siblings, 1 reply; 265+ messages in thread

From: Alvaro Herrera @ 2019-02-01 10:01 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers

Please rebase.

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-01 14:09  David Rowley <david.rowley@2ndquadrant.com>
  parent: Alvaro Herrera <alvherre@2ndquadrant.com>
  0 siblings, 0 replies; 265+ messages in thread

From: David Rowley @ 2019-02-01 14:09 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers

On Fri, 1 Feb 2019 at 22:52, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
>
> On 2019-Jan-31, David Rowley wrote:
> >
> > I think we could make the 0001 patch a bit smaller if we were to apply
> > the attached first.
> >
> > Details in the commit message.
> >
> > What do you think?
>
> Amit didn't say what he thinks, but I think it looks good and the
> rationale for it makes sense, so I pushed it.  I only amended some
> comments a little bit.

Thanks.


-- 
 David Rowley                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-01 14:11  David Rowley <david.rowley@2ndquadrant.com>
  parent: Alvaro Herrera <alvherre@2ndquadrant.com>
  0 siblings, 1 reply; 265+ messages in thread

From: David Rowley @ 2019-02-01 14:11 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers

On Fri, 1 Feb 2019 at 23:01, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
> Please rebase.

I had a short list of other things I noticed when making a partial
pass over the patch again.

I may as well send these now if there's a new version on the way:

1. I think it's okay to convert the following:

/*
* Adjust all_baserels to replace the original target relation with the
* child target relation.  Copy it before modifying though.
*/
subroot->all_baserels = bms_copy(root->all_baserels);
subroot->all_baserels = bms_del_member(subroot->all_baserels,
   root->parse->resultRelation);
subroot->all_baserels = bms_add_member(subroot->all_baserels,
   subroot->parse->resultRelation);

into:
/* Adjust all_baserels */
subroot->all_baserels = adjust_child_relids(root->all_baserels, 1, &appinfo);


2. Any reason to do:

/*
* Generate access paths for the entire join tree.
*
* For UPDATE/DELETE on an inheritance parent, join paths should be
* generated for each child result rel separately.
*/
if (root->parse->resultRelation &&
root->simple_rte_array[root->parse->resultRelation]->inh)

instead of just checking: if (root->inherited_update)

3. This seems like useless code in set_inherit_target_rel_sizes().

/*
* If parallelism is allowable for this query in general, see whether
* it's allowable for this childrel in particular.  For consistency,
* do this before calling set_rel_size() for the child.
*/
if (root->glob->parallelModeOK)
set_rel_consider_parallel(subroot, childrel, childRTE);


parallelModeOK is only ever set for SELECT.  Likely it's fine just to
replace these with:

+               /* We don't consider parallel paths for UPDATE/DELETE
statements */
+               childrel->consider_parallel = false;

or perhaps it's fine to leave it out since build_simple_rel() sets it to false.

-- 
 David Rowley                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-02 13:52  Amit Langote <amitlangote09@gmail.com>
  parent: David Rowley <david.rowley@2ndquadrant.com>
  0 siblings, 1 reply; 265+ messages in thread

From: Amit Langote @ 2019-02-02 13:52 UTC (permalink / raw)
  To: David Rowley <david.rowley@2ndquadrant.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers

Thanks David for the patch that Alvaro committed yesterday.  Agree
that it's a good idea.

On Fri, Feb 1, 2019 at 3:11 PM David Rowley
<david.rowley@2ndquadrant.com> wrote:
> On Fri, 1 Feb 2019 at 23:01, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
> > Please rebase.
>
> I had a short list of other things I noticed when making a partial
> pass over the patch again.
>
> I may as well send these now if there's a new version on the way:

Thanks.

> 1. I think it's okay to convert the following:
>
> /*
> * Adjust all_baserels to replace the original target relation with the
> * child target relation.  Copy it before modifying though.
> */
> subroot->all_baserels = bms_copy(root->all_baserels);
> subroot->all_baserels = bms_del_member(subroot->all_baserels,
>    root->parse->resultRelation);
> subroot->all_baserels = bms_add_member(subroot->all_baserels,
>    subroot->parse->resultRelation);
>
> into:
> /* Adjust all_baserels */
> subroot->all_baserels = adjust_child_relids(root->all_baserels, 1, &appinfo);

Makes sense, done.

> 2. Any reason to do:
>
> /*
> * Generate access paths for the entire join tree.
> *
> * For UPDATE/DELETE on an inheritance parent, join paths should be
> * generated for each child result rel separately.
> */
> if (root->parse->resultRelation &&
> root->simple_rte_array[root->parse->resultRelation]->inh)
>
> instead of just checking: if (root->inherited_update)

Good reminder, done.

> 3. This seems like useless code in set_inherit_target_rel_sizes().
>
> /*
> * If parallelism is allowable for this query in general, see whether
> * it's allowable for this childrel in particular.  For consistency,
> * do this before calling set_rel_size() for the child.
> */
> if (root->glob->parallelModeOK)
> set_rel_consider_parallel(subroot, childrel, childRTE);
>
>
> parallelModeOK is only ever set for SELECT.  Likely it's fine just to
> replace these with:
>
> +               /* We don't consider parallel paths for UPDATE/DELETE
> statements */
> +               childrel->consider_parallel = false;
>
> or perhaps it's fine to leave it out since build_simple_rel() sets it to false.

OK, removed that code.

Attached updated patches.  It took me a bit longer than expected to
rebase the patches as I hit a mysterious bug that I couldn't pinpoint
until this afternoon.

One big change is related to how ECs are transferred to child
PlannerInfos.  As David suggested upthread, I created a block in
adjust_appendrel_attrs_mutator that creates a translated copy of a
given EC containing wherein the parent expression in the original
ec_members list is replaced by the corresponding child expression.
With that in place, we no longer need the changes to
add_child_rel_equivalences().  Instead there's just:
subroot->eq_classes = adjust_appendrel_attrs(root, root->eq_classes,
...), just as David described upthread.

Thanks,
Amit

Attachments: download all (4 files, .tar.gz)

v19-0004-Do-not-lock-all-partitions-at-the-beginning.patch (application/octet-stream, 1.9K) download From 949991fc95bb040425570b8077176e0d0e3341c7 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 12 Oct 2018 19:12:53 +0900 Subject: [PATCH v19 4/4] Do not lock all partitions at the beginning --- src/backend/optimizer/util/inherit.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 5af327b720..95a9bb6247 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -332,10 +332,6 @@ expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, * * Partitions are added to the query in order in which they are found in * the parent's PartitionDesc. - * - * Note: even though only the unpruned partitions will be added to the - * resulting plan, this still locks *all* partitions via find_all_inheritors - * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, @@ -349,10 +345,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, parentrel->inh_root_parent : parentRTindex; - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - /* * Initialize partitioned_child_rels to contain this RT index. * @@ -389,8 +381,11 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, RangeTblEntry *childrte; Index childRTindex; - /* Open rel; we already have required locks */ - newrelation = table_open(childOID, NoLock); + /* + * Open rel; this's the first time of opening partitions for this + * query, so take the appropriate locks. + */ + newrelation = table_open(childOID, lockmode); Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* -- 2.17.2 (Apple Git-113)
v19-0002-Lazy-creation-of-RTEs-for-inheritance-children.patch (application/octet-stream, 93.9K) download From e448cde136542f7afc34d3e6bf47f66e992aa34b Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 26 Oct 2018 16:45:59 +0900 Subject: [PATCH v19 2/4] Lazy creation of RTEs for inheritance children Currently, expand_inherited_tables called from subquery_planner opens all inheritance child tables and adds them to the query's range table and PlannerInfo in the form of AppendRelInfo's initially and later RelOptInfo's. For partitioning, that's pretty wasteful, because it's possible to determine only the partitions that will need to be scanned using partition pruning, which doesn't require opening the partitions themselves. However, the point at which expand_inherited_tables is called currently is too early to perform partition pruning. This commit rearranges things within the planner so that expand_inherited_tables can be called from query_planner after the latter has finished setting up the information needed to perform partition pruning. However that means the PlannerInfo arrays that store RangeTblEntry's, RelOptInfo's, and AppendRelInfo's need to be expanded if inheritance expansion leads to adding more relations to planning. There are also consequences around targetlist expansion. For example, preprocess_targetlist cannot add the junk columns entries needed for row mark handling until all of the inheritance children have been determined, because different children may need different types of junk columns to be added. So, the approach of late inheritance expansion required some interface tweaks for preprocess_targetlist(). For partitioning, although we don't create a RangeTblEntry and RelOptInfo for pruned partitions at make_one_rel time, partitionwise join code relies on the fact that even though partitions may have been pruned, they'd still own a RelOptInfo to handle the outer join case where the pruned partition appears on the nullable side of join. Partitionwise join code deals with that by allocating dummy RelOptInfos for pruned partitions that are based mostly on their parent's properties. There are some regression test diffs: 1. Caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child, as seen in the partition_aggregate.out test output. 2. Those in postgres_fdw.out are caused by the fact that junk columns required for row marking are added to reltarget->exprs later than user columns, because the row marking junk columns aren't added until the inheritance is expanded which as of this commit is later than it used to be as noted above. --- .../postgres_fdw/expected/postgres_fdw.out | 32 +- src/backend/nodes/outfuncs.c | 1 + src/backend/optimizer/path/allpaths.c | 239 +---- src/backend/optimizer/path/joinrels.c | 61 ++ src/backend/optimizer/plan/initsplan.c | 58 -- src/backend/optimizer/plan/planmain.c | 10 + src/backend/optimizer/plan/planner.c | 72 +- src/backend/optimizer/plan/setrefs.c | 6 + src/backend/optimizer/prep/preptlist.c | 15 +- src/backend/optimizer/prep/prepunion.c | 3 + src/backend/optimizer/util/appendinfo.c | 45 +- src/backend/optimizer/util/inherit.c | 839 +++++++++++++----- src/backend/optimizer/util/plancat.c | 47 +- src/backend/optimizer/util/relnode.c | 107 ++- src/backend/partitioning/partprune.c | 44 +- src/include/nodes/pathnodes.h | 19 +- src/include/optimizer/appendinfo.h | 7 +- src/include/optimizer/pathnode.h | 1 + src/include/optimizer/plancat.h | 4 +- src/include/optimizer/prep.h | 3 +- .../regress/expected/partition_aggregate.out | 4 +- 21 files changed, 950 insertions(+), 667 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index b3894d0760..fde1a9cdca 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7072,15 +7072,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7110,15 +7110,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7147,15 +7147,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid @@ -7165,15 +7165,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (39 rows) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index e7a65985cf..a58ec885d8 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2271,6 +2271,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_BOOL_FIELD(consider_partitionwise_join); WRITE_BITMAPSET_FIELD(top_parent_relids); WRITE_NODE_FIELD(partitioned_child_rels); + WRITE_UINT_FIELD(inh_root_parent); } static void diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index f07db81a80..994cca7110 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -45,7 +45,6 @@ #include "optimizer/tlist.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -145,9 +144,6 @@ static void subquery_push_qual(Query *subquery, static void recurse_push_qual(Node *setOp, Query *topquery, RangeTblEntry *rte, Index rti, Node *qual); static void remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel); -static bool apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, - RelOptInfo *childrel, - RangeTblEntry *childRTE, AppendRelInfo *appinfo); /* @@ -387,6 +383,14 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, } else if (rte->inh) { + /* + * expand_inherited_tables may have proved that the relation is empty. + * For example, if it's a partitioned table with 0 partitions or all + * of its partitions are pruned. In that case nothing to do here. + */ + if (IS_DUMMY_REL(rel)) + return; + /* * If it's a target relation, set the sizes of children instead. * Otherwise, we'll append the outputs of children, so process it as @@ -968,29 +972,12 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, int parentRTindex = rti; bool has_live_children = false; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); Assert(IS_SIMPLE_REL(rel)); - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - foreach(l, root->append_rel_list) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); @@ -1001,7 +988,6 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, ListCell *lc; List *translated_exprs, *child_target_exprs; - bool childpruned; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1009,32 +995,23 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); + Assert(childrel != NULL); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - childpruned = (did_pruning && - !bms_is_member(appinfo->child_relid, live_children)); - /* - * Unless the child is pruned, 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 baserestrictinfo quals. + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals or quals that contradict its + * constraints. */ - if (childpruned || - !apply_child_basequals(root, rel, childrel, childRTE, appinfo) || - relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* This partition needn't be scanned; skip it. */ - set_dummy_rel_pathlist(childrel); + if (IS_DUMMY_REL(childrel)) continue; - } /* * Add missing Vars to child's reltarget. @@ -1192,40 +1169,12 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); Assert(IS_SIMPLE_REL(rel)); - /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, we will bubble up - * the indexes of partitioned relations that appear down in the tree, so - * that when we've created Paths for all the children, the root - * partitioned table's list will contain all such indexes. - */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - rel->partitioned_child_rels = list_make1_int(rti); - - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - /* * If this is a partitioned baserel, set the consider_partitionwise_join * flag; currently, we only consider partitionwise joins with the baserel @@ -1265,7 +1214,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, RelOptInfo *childrel; ListCell *parentvars; ListCell *childvars; - bool childpruned; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1273,34 +1221,26 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); + Assert(childrel != NULL); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - childpruned = (did_pruning && - !bms_is_member(appinfo->child_relid, live_children)); /* - * Unless the child is pruned, 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 baserestrictinfo quals. + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals or quals that contradict its + * constraints. */ - if (childpruned || - !apply_child_basequals(root, rel, childrel, childRTE, appinfo) || - relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* This partition needn't be scanned; skip it. */ - set_dummy_rel_pathlist(childrel); + if (IS_DUMMY_REL(childrel)) continue; - } /* - * CE failed, so finish copying/modifying targetlist and join quals. + * Copy/Modify targetlist. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -2837,6 +2777,7 @@ inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) continue; childrel = find_base_rel(root, appinfo->child_relid); + Assert(childrel != NULL); /* Ignore excluded/pruned children. */ if (IS_DUMMY_REL(childrel)) @@ -3941,134 +3882,6 @@ generate_partitionwise_join_paths(PlannerInfo *root, RelOptInfo *rel) list_free(live_children); } -/* - * apply_child_basequals - * Populate childrel's quals based on rel's quals, translating them using - * appinfo. - * - * If any of the resulting clauses evaluate to false or NULL, we return false - * and don't apply any quals. Caller can mark the relation as a dummy rel in - * this case, since it needn't be scanned. - * - * If any resulting clauses evaluate to true, they're unnecessary and we don't - * apply then. - */ -static bool -apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, - RelOptInfo *childrel, RangeTblEntry *childRTE, - AppendRelInfo *appinfo) -{ - List *childquals; - Index cq_min_security; - ListCell *lc; - - /* - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities for - * const-simplification, and perhaps even pseudoconstant quals. Therefore, - * transform each RestrictInfo separately to see if it reduces to a - * constant or pseudoconstant. (We must process them separately to keep - * track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - return false; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. (Currently - * this can only happen in appendrels originating from UNION ALL; - * inheritance child tables don't have their own securityQuals, see - * expand_inherited_rtentry().) Pull any such securityQuals up into the - * baserestrictinfo for the child. This is similar to - * process_security_barrier_quals() for the parent rel, except that we - * can't make any general deductions from such quals, since they don't - * hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - return true; -} - /***************************************************************************** * DEBUG SUPPORT *****************************************************************************/ diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index a02321486f..6f77d2a0f4 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -21,6 +21,7 @@ #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" +#include "optimizer/tlist.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -52,6 +53,9 @@ static SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, Relids left_relids, Relids right_relids); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + int partidx); /* @@ -1384,6 +1388,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (IS_SIMPLE_REL(rel1) && child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts); + if (IS_SIMPLE_REL(rel1) && child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts); + /* * If a child table has consider_partitionwise_join=false, it means * that it's a dummy relation for which we skipped setting up tlist @@ -1680,3 +1689,55 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +static RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx) +{ + RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + AppendRelInfo *appinfo = make_append_rel_info(parent, parentrte, + parent->tupdesc, + parentrte->relid, + parent->reltype, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index d6ffa7869d..5e8a8b1eee 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -617,64 +617,6 @@ create_lateral_join_info(PlannerInfo *root) bms_add_member(brel2->lateral_referencers, rti); } } - - /* - * Lastly, propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - */ - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - RangeTblEntry *brte = root->simple_rte_array[rti]; - - /* - * Skip empty slots. Also skip non-simple relations i.e. dead - * relations. - */ - if (brel == NULL || !IS_SIMPLE_REL(brel)) - continue; - - /* - * In the case of table inheritance, the parent RTE is directly linked - * to every child table via an AppendRelInfo. In the case of table - * partitioning, the inheritance hierarchy is expanded one level at a - * time rather than flattened. Therefore, an other member rel that is - * a partitioned table may have children of its own, and must - * therefore be marked with the appropriate lateral info so that those - * children eventually get marked also. - */ - Assert(brte); - if (brel->reloptkind == RELOPT_OTHER_MEMBER_REL && - (brte->rtekind != RTE_RELATION || - brte->relkind != RELKIND_PARTITIONED_TABLE)) - continue; - - if (brte->inh) - { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); - RelOptInfo *childrel; - - if (appinfo->parent_relid != rti) - continue; - childrel = root->simple_rel_array[appinfo->child_relid]; - Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - Assert(childrel->direct_lateral_relids == NULL); - childrel->direct_lateral_relids = brel->direct_lateral_relids; - Assert(childrel->lateral_relids == NULL); - childrel->lateral_relids = brel->lateral_relids; - Assert(childrel->lateral_referencers == NULL); - childrel->lateral_referencers = brel->lateral_referencers; - } - } - } } diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index acb1461624..7a3b8171c5 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -281,6 +281,16 @@ query_planner(PlannerInfo *root, List *tlist, root->all_baserels = bms_add_member(root->all_baserels, brel->relid); } + /* + * Expand RT entries that represent inherited or partitioned tables. + * This will perform partition pruning on partitioned tables in the + * original range table and also recursively on any child partitioned + * tables that were added by the expansion of the original parent(s). + * As new entries are added to the range table, various arrays in the + * PlannerInfo will be expanded accordingly. + */ + expand_inherited_tables(root); + /* * Add child subroots needed to use during planning for individual child * targets diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 71aa8dc72a..c90b62bea1 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -25,6 +25,7 @@ #include "access/table.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -717,26 +718,17 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } - /* - * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). - */ - preprocess_rowmarks(root); - - /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain RTE_RELATION entries, so it's OK (and marginally more efficient) - * to do it after checking for joins and other special RTEs. We must do - * this after pulling up subqueries, else we'd fail to handle inherited - * tables in subqueries. - */ - expand_inherited_tables(root); - /* * Now that we have figured out "actual" inheritance situation of the * relations, set whether the query is an inherited UPDATE/DELETE. @@ -744,6 +736,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->inherited_update = (parse->resultRelation && rt_fetch(parse->resultRelation, parse->rtable)->inh); + /* + * Preprocess RowMark information. We need to do this after subquery + * pullup (so that all non-inherited RTEs are present). + */ + preprocess_rowmarks(root); + /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to @@ -1257,7 +1255,7 @@ inheritance_planner(PlannerInfo *root) root->unexpanded_tlist = list_copy(root->parse->targetList); /* We haven't expanded inheritance yet, so pass false. */ - tlist = preprocess_targetlist(root); + tlist = preprocess_targetlist(root, false); root->processed_tlist = tlist; qp_extra.tlist = tlist; qp_extra.activeWindows = NIL; @@ -1267,9 +1265,11 @@ inheritance_planner(PlannerInfo *root) /* * If it turned out during query planning that all the children are dummy * (pruned or excluded by constraints), no need to do the steps below. - * Let grouping_planner finish up the final path. + * For regular inheritance case, it's possible that we only need to modify + * the parent table. Let grouping_planner finish up the final path. */ - if (IS_DUMMY_REL(planned_rel)) + parent_rte = planner_rt_fetch(top_parentRTindex, root); + if (!parent_rte->inh || IS_DUMMY_REL(planned_rel)) { grouping_planner(root, false, planned_rel, 0.0); return; @@ -1285,7 +1285,6 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = planner_rt_fetch(top_parentRTindex, root); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1614,14 +1613,19 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, */ if (planned_rel == NULL) { - tlist = preprocess_targetlist(root); + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root, false); /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation - * so that we can transfer its decoration (resnames etc) to the - * topmost tlist of the finished Plan. + * We are now mostly done hacking up the query's targetlist. More + * columns might be added during query_planner after inheritance + * expansion in make_one_rel, because some children may require + * different column than the parent for row mark handling; see + * add_rowmark_junk_columns(). Most of the remaining planning + * work will be done with the PathTarget representation of tlists, + * but save aside the full representation so that we can transfer + * its decoration (resnames etc) to the topmost tlist of the + * finished Plan. */ root->processed_tlist = tlist; } @@ -2397,7 +2401,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2422,7 +2426,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } @@ -6859,6 +6863,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -6960,6 +6968,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 0213a37670..154ccda432 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -326,6 +326,12 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) UPPERREL_FINAL, NULL))) add_rtes_to_flat_rtable(rel->subroot, true); } + /* + * A NULL rel also means an unplanned subquery rte, so apply + * flatten_unplanned_rtes. + */ + else + flatten_unplanned_rtes(glob, rte); } rti++; } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 5392d1a561..41a57d16b2 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -65,9 +65,13 @@ static List *expand_targetlist(List *tlist, int command_type, * * As a side effect, if there's an ON CONFLICT UPDATE clause, its targetlist * is also preprocessed (and updated in-place). + * + * inheritance_expanded specifies whether inheritance tables are expanded + * as of calling this function. Caller must specify it because that affects + * which row marking related junk columns get added to the targetlist. */ List * -preprocess_targetlist(PlannerInfo *root) +preprocess_targetlist(PlannerInfo *root, bool inheritance_expanded) { Query *parse = root->parse; int result_relation = parse->resultRelation; @@ -134,6 +138,15 @@ preprocess_targetlist(PlannerInfo *root) if (rc->rti != rc->prti) continue; + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which will change the parent row + * mark's allMarkTypes fields. + */ + if (rc->isParent && !inheritance_expanded) + continue; + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) { /* Need to fetch TID */ diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 55eeb5127c..7bed914f35 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -31,12 +31,15 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" #include "optimizer/cost.h" +#include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index 7447853db9..c72251aea2 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -33,10 +33,10 @@ typedef struct AppendRelInfo **appinfos; } adjust_appendrel_attrs_context; -static void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, adjust_appendrel_attrs_context *context); static List *adjust_inherited_tlist(List *tlist, @@ -48,18 +48,20 @@ static List *adjust_inherited_tlist(List *tlist, * Build an AppendRelInfo for the parent-child pair */ AppendRelInfo * -make_append_rel_info(Relation parentrel, Relation childrel, - Index parentRTindex, Index childRTindex) +make_append_rel_info(RelOptInfo *parent, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childtype, + Index childRTindex) { AppendRelInfo *appinfo = makeNode(AppendRelInfo); - appinfo->parent_relid = parentRTindex; + appinfo->parent_relid = parent->relid; appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->rd_rel->reltype; - appinfo->child_reltype = childrel->rd_rel->reltype; - make_inh_translation_list(parentrel, childrel, childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = RelationGetRelid(parentrel); + appinfo->parent_reltype = parent->reltype; + appinfo->child_reltype = childtype; + make_inh_translation_list(parent->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; return appinfo; } @@ -72,14 +74,11 @@ make_append_rel_info(Relation parentrel, Relation childrel, * For paranoia's sake, we match type/collation as well as attribute name. */ static void -make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, - List **translated_vars) +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) { List *vars = NIL; - TupleDesc old_tupdesc = RelationGetDescr(oldrelation); - TupleDesc new_tupdesc = RelationGetDescr(newrelation); - Oid new_relid = RelationGetRelid(newrelation); int oldnatts = old_tupdesc->natts; int newnatts = new_tupdesc->natts; int old_attno; @@ -109,7 +108,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, * When we are generating the "translation list" for the parent table * of an inheritance set, no need to search for matches. */ - if (oldrelation == newrelation) + if (from_rel == to_rel) { vars = lappend(vars, makeVar(newvarno, (AttrNumber) (old_attno + 1), @@ -135,10 +134,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, { HeapTuple newtup; - newtup = SearchSysCacheAttName(new_relid, attname); + newtup = SearchSysCacheAttName(to_rel, attname); if (!newtup) elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; ReleaseSysCache(newtup); @@ -148,10 +147,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, /* Found it, check type and collation match */ if (atttypid != att->atttypid || atttypmod != att->atttypmod) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); if (attcollation != att->attcollation) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); vars = lappend(vars, makeVar(newvarno, (AttrNumber) (new_attno + 1), diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 68ec4aab6a..363892466b 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -18,33 +18,47 @@ #include "access/table.h" #include "catalog/partition.h" #include "catalog/pg_inherits.h" +#include "catalog/pg_type.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" #include "optimizer/inherit.h" +#include "optimizer/optimizer.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" +#include "optimizer/plancat.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" +#include "partitioning/partprune.h" #include "utils/rel.h" static PlannerInfo *create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo); -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); +static void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti); +static void expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, + RangeTblEntry *rte, Index rti, RelOptInfo *rel); static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); - +static RelOptInfo *build_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); +static bool apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *childrel, + RangeTblEntry *childRTE, AppendRelInfo *appinfo); /* * expand_inherited_tables @@ -52,37 +66,145 @@ static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, * into an "append relation". At the conclusion of this process, * the "inh" flag is set in all and only those RTEs that are append * relation parents. + * + * Note that although we're calling the combined output of inheritance set + * relations an "append relation" here, the caller may not always want to + * combine the relations. For example, if the parent of the inheritance + * set is the query's target relation, each child relation is processed + * on its own as the query's target relation. */ void expand_inherited_tables(PlannerInfo *root) { - Index nrtes; + int orig_rtable_size; Index rti; - ListCell *rl; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; /* * expand_inherited_rtentry may add RTEs to parse->rtable. The function is * expected to recursively handle any RTEs that it creates with inh=true. * So just scan as far as the original end of the rtable list. */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) + for (rti = 1; rti < orig_rtable_size; rti++) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + if (rte->inh) + expand_inherited_rtentry(root, brel, rte, rti); } } /* * expand_inherited_rtentry - * Check whether a rangetable entry represents an inheritance set. - * If so, add entries for all the child tables to the query's - * rangetable, and build AppendRelInfo nodes for all the child tables - * and add them to root->append_rel_list. If not, clear the entry's - * "inh" flag to prevent later code from looking for AppendRelInfos. + * This initializes RelOptInfos for inheritance child relations if the + * passed-in relation has any + * + * 'rel' is the parent relation, whose range table entry ('rte') has been + * marked to require adding children. Parent could either be a subquery (if + * we flattened UNION ALL query) or a table that's known to have (or once had) + * inheritance children. The latter consists of both regular inheritance + * parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as a child to be scanned as part of the + * inheritance set. The original RTE is considered to represent the whole + * inheritance set. + */ +static void +expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_inheritance_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_nonpartitioned_inherited_rtentry(root, rte, rti, rel); + } +} + +/* + * expand_nonpartitioned_inherited_rtentry + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. * * Note that the original RTE is considered to represent the whole * inheritance set. The first of the generated RTEs is an RTE for the same @@ -93,55 +215,35 @@ expand_inherited_tables(PlannerInfo *root) * regular inheritance, a parent RTE must always have at least two associated * AppendRelInfos: one corresponding to the parent table as a simple member of * inheritance set and one or more corresponding to the actual children. - * Since a partitioned table is not scanned, it might have only one associated - * AppendRelInfo. */ static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, + RangeTblEntry *rte, + Index rti, + RelOptInfo *rel) { Oid parentOID; PlanRowMark *oldrc; - Relation oldrelation; - LOCKMODE lockmode; + LOCKMODE lockmode = rte->rellockmode; List *inhOIDs; ListCell *l; + int num_children; + int num_children_added = 0; - /* Does RT entry allow inheritance? */ - if (!rte->inh) - return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } - /* Fast path for common case of childless table */ + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); parentOID = rte->relid; - if (!has_subclass(parentOID)) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * The rewriter should already have obtained an appropriate lock on each - * relation named in the query. However, for each child relation we add - * to the query, we must obtain an appropriate lock, because this will be - * the first use of those relations in the parse/rewrite/plan pipeline. - * Child rels should use the same lockmode as their parent. - */ - lockmode = rte->rellockmode; /* Scan for all members of inheritance set, acquire needed locks */ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); /* * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite above has_subclass() check, if table - * once had a child but no longer does. + * case. This could happen despite has_subclass() check performed by + * subquery_planner, if table once had a child but no longer does. */ - if (list_length(inhOIDs) < 2) + num_children = list_length(inhOIDs); + if (num_children < 2) { /* Clear flag before returning */ rte->inh = false; @@ -149,216 +251,221 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } /* - * If parent relation is selected FOR UPDATE/SHARE, we need to mark its - * PlanRowMark as isParent = true, and generate a new PlanRowMark for each - * child. + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. */ oldrc = get_plan_rowmark(root->rowMarks, rti); - if (oldrc) - oldrc->isParent = true; + Assert(oldrc == NULL || oldrc->isParent); /* - * Must open the parent relation to examine its tupdesc. We need not lock - * it; we assume the rewriter already did. + * Must expand PlannerInfo arrays by num_children before we can add + * children. */ - oldrelation = table_open(parentOID, NoLock); + Assert(num_children > 0); + expand_planner_arrays(root, num_children); - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) + foreach(l, inhOIDs) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); - - /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. - */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); - } - else - { - List *appinfos = NIL; + Oid childOID = lfirst_oid(l); + Relation newrelation; RangeTblEntry *childrte; Index childRTindex; + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. */ - foreach(l, inhOIDs) + if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = table_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - table_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - table_close(newrelation, NoLock); + table_close(newrelation, lockmode); + continue; } - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); + (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation, &childrte, + &childRTindex); + Assert(childrte != NULL); + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + Assert(childRTindex > 0); + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; } - table_close(oldrelation, NoLock); + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } } /* * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) + Index parentRTindex, RelOptInfo *parentrel) { + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; - check_stack_depth(); - - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + if (bms_num_members(partindexes) > 0) + expand_planner_arrays(root, bms_num_members(partindexes)); /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. */ - if (partdesc->nparts == 0) - { - parentrte->inh = false; - return; - } + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); - for (i = 0; i < partdesc->nparts; i++) + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) { - Oid childOID = partdesc->oids[i]; - Relation childrel; + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + RangeTblEntry *childrte; + Index childRTindex; /* Open rel; we already have required locks */ - childrel = table_open(childOID, NoLock); + newrelation = table_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation, + &childrte, &childRTindex); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + /* Close child relations, but keep locks */ + table_close(newrelation, NoLock); + + /* If the child is partitioned itself, expand it too. */ + if (childrel->part_scheme) + { + Assert(childrte->inh); expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); + childrel); + } + } - /* Close child relation, but keep locks */ - table_close(childrel, NoLock); + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); } } /* - * expand_single_inheritance_child - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) + * The return value is the RelOptInfo that's added. * * PlanRowMarks still carry the top-parent's RTI, and the top-parent's * allMarkTypes field still accumulates values from all descendents. * * "parentrte" and "parentRTindex" are immediate parent's RTE and * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. */ -static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); Oid childOID = RelationGetRelid(childrel); RangeTblEntry *childrte; Index childRTindex; AppendRelInfo *appinfo; + RelOptInfo *childrelopt; /* * Build an RTE for the child, and attach to query's rangetable list. We * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh = false. Also, set requiredPerms to zero since - * all required permissions checks are done on the original RTE. Likewise, - * set the child's securityQuals to empty, because we only want to apply - * the parent's RLS conditions regardless of what RLS properties + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties * individual children may have. (This is an intentional choice to make * inherited RLS work like regular permissions checks.) The parent * securityQuals will be propagated to children along with other base @@ -368,49 +475,42 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childrte_p = childrte; childrte->relid = childOID; childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) - childrte->inh = true; - else - childrte->inh = false; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); childrte->requiredPerms = 0; childrte->securityQuals = NIL; parse->rtable = lappend(parse->rtable, childrte); childRTindex = list_length(parse->rtable); *childRTindex_p = childRTindex; - /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. - */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) - { - appinfo = make_append_rel_info(parentrel, childrel, - parentRTindex, childRTindex); - *appinfos = lappend(*appinfos, appinfo); + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + RelationGetDescr(childrel), + RelationGetRelid(childrel), + RelationGetForm(childrel)->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); } /* @@ -442,6 +542,143 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need to create one, + * because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_inheritance_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; +} + +/* + * build_inheritance_child_rel + * Build a RelOptInfo for child relation of an inheritance set + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields based on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turn out to be self-contradictory, + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + */ + if (!apply_child_basequals(root, parent, childrel, childRTE, appinfo) || + relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; } /* @@ -584,7 +821,7 @@ create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) * column, filling placeholder entries for dropped columns, etc., all of * which occurs with the child's TupleDesc. */ - tlist = preprocess_targetlist(subroot); + tlist = preprocess_targetlist(subroot, true); subroot->processed_tlist = tlist; /* Add any newly added Vars to the child RelOptInfo. */ @@ -599,3 +836,131 @@ create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) return subroot; } + +/* + * apply_child_basequals + * Populate childrel's quals based on rel's quals, translating them using + * appinfo. + * + * If any of the resulting clauses evaluate to false or NULL, we return false + * and don't apply any quals. Caller can mark the relation as a dummy rel in + * this case, since it needn't be scanned. + * + * If any resulting clauses evaluate to true, they're unnecessary and we don't + * apply then. + */ +static bool +apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *childrel, RangeTblEntry *childRTE, + AppendRelInfo *appinfo) +{ + List *childquals; + Index cq_min_security; + ListCell *lc; + + /* + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities for + * const-simplification, and perhaps even pseudoconstant quals. Therefore, + * transform each RestrictInfo separately to see if it reduces to a + * constant or pseudoconstant. (We must process them separately to keep + * track of the security level of each qual.) + */ + childquals = NIL; + cq_min_security = UINT_MAX; + foreach(lc, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, + (Node *) rinfo->clause, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + return false; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + /* track minimum security level among child quals */ + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might have + * securityQuals associated with this particular child node. (Currently + * this can only happen in appendrels originating from UNION ALL; + * inheritance child tables don't have their own securityQuals, see + * expand_inherited_rtentry().) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except that we + * can't make any general deductions from such quals, since they don't + * hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* not likely that we'd see constants here, so no check */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, false, false, + security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* + * OK, we've got all the baserestrictinfo quals for this child. + */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + return true; +} diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index f2ee83c81b..f3f7bb29c6 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -105,20 +105,20 @@ static void set_baserel_partition_key_exprs(Relation relation, * important for it. */ void -get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) +get_relation_info(PlannerInfo *root, RangeTblEntry *rte, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; bool hasindex; List *indexinfos = NIL; + bool inhparent = rte->inh; /* * We need not lock the relation since it was already locked, either by * the rewriter or when expand_inherited_rtentry() added it to the query's * rangetable. */ - relation = table_open(relationObjectId, NoLock); + relation = heap_open(rte->relid, NoLock); /* Temporary and unlogged relations are inaccessible during recovery. */ if (!RelationNeedsWAL(relation) && RecoveryInProgress()) @@ -444,11 +444,32 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, get_relation_foreign_keys(root, rel, relation, inhparent); /* - * Collect info about relation's partitioning scheme, if any. Only - * inheritance parents may be partitioned. + * Collect some additional information for inheritance parents. */ - if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - set_relation_partition_info(root, rel, relation); + if (inhparent) + { + /* + * We'll need the TupleDesc when initializing the child relation. + * A copy is being made because concurrent changes might drop + * the relcache entry. That's possible because ALTER TABLE + * child_table NO INHERIT parent_table only requires an + * AccessShareLock on parent_table. + */ + rel->tupdesc = CreateTupleDescCopy(RelationGetDescr(relation)); + rel->reltype = RelationGetForm(relation)->reltype; + + /* + * If partitioned, also save the information of partitioning scheme, + * and whether the query updates any of the partition key columns. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + set_relation_partition_info(root, rel, relation); + root->partColsUpdated |= has_partition_attrs(relation, + rte->updatedCols, + NULL); + } + } table_close(relation, NoLock); @@ -458,7 +479,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * removing an index, or adding a hypothetical index to the indexlist. */ if (get_relation_info_hook) - (*get_relation_info_hook) (root, relationObjectId, inhparent, rel); + (*get_relation_info_hook) (root, rte->relid, rte->inh, rel); } /* @@ -1845,16 +1866,20 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; + + /* + * Since we must've taken a lock on the table, it's okay to simply copy + * the pointers to relcache data here. + */ + rel->part_oids = partdesc->oids; + rel->boundinfo = partdesc->boundinfo; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index f04c6b76f4..09b1b8075a 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -131,6 +131,50 @@ setup_append_rel_array(PlannerInfo *root) } } +/* + * expand_planner_arrays + * Expand the PlannerInfo arrays by add_size members and initialize the + * the newly added bytes with zero + */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size; + + Assert(add_size > 0); + + new_size = root->simple_rel_array_size + add_size; + + /* Expand various arrays and 0-initialize added bytes. */ + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + + if (root->append_rel_array) + { + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + } + else + { + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + } + + root->simple_rel_array_size = new_size; +} + /* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. @@ -195,6 +239,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->joininfo = NIL; rel->has_eclass_joins = false; rel->consider_partitionwise_join = false; /* might get changed later */ + rel->top_parent_relids = NULL; /* might be changed later */ rel->part_scheme = NULL; rel->nparts = 0; rel->boundinfo = NULL; @@ -203,6 +248,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->partexprs = NULL; rel->nullable_partexprs = NULL; rel->partitioned_child_rels = NIL; + rel->inh_root_parent = 0; /* might be changed later */ /* * Pass top parent's relids down the inheritance hierarchy. If the parent @@ -216,16 +262,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->top_parent_relids = parent->top_parent_relids; else rel->top_parent_relids = bms_copy(parent->relids); + + /* + * For inheritance child relations, we also set inh_root_parent. + * Note that 'parent' might itself be a child (a sub-partitioned + * partition), in which case we simply use its value of + * inh_root_parent. + */ + if (parent->rtekind == RTE_RELATION) + rel->inh_root_parent = parent->inh_root_parent > 0 ? + parent->inh_root_parent : + parent->relid; } - else - rel->top_parent_relids = NULL; /* Check type of rtable entry */ switch (rte->rtekind) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte, rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -273,52 +328,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); - /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. - */ - if (rte->inh) - { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); - - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; - } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); - } - return rel; } diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 8c9721935d..472a6cd331 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -45,6 +45,8 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "partitioning/partprune.h" @@ -433,17 +435,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -537,29 +545,33 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ if (rel->nparts == 0) return NULL; + /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. @@ -587,15 +599,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index b46947ffe9..1d36bda039 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -15,6 +15,7 @@ #define PATHNODES_H #include "access/sdir.h" +#include "access/tupdesc.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/params.h" @@ -383,7 +384,6 @@ struct PlannerInfo }; - /* * In places where it's known that simple_rte_array[] must have been prepared * already, we just index into it to fetch RTEs. In code that might be @@ -736,11 +736,28 @@ typedef struct RelOptInfo int nparts; /* number of partitions */ struct PartitionBoundInfoData *boundinfo; /* Partition bounds */ List *partition_qual; /* partition constraint */ + Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ + + /* + * For inheritance children, this is the RT index of inheritance table + * mentioned in the query from which this relation originated. + * top_parent_relids cannot be used for this, because if the inheritance + * root table is itself under UNION ALL, top_parent_relids contains the + * RT index of UNION ALL parent subquery. + */ + Index inh_root_parent; + + /* + * Set only if this is an inheritance parent relation. This information + * is needed when initializing the planning info for children. + */ + TupleDesc tupdesc; /* A "copy" of the table's tuple desriptor */ + Oid reltype; /* Table's reltype */ } RelOptInfo; /* diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h index 790914c1b0..c0b8db9a62 100644 --- a/src/include/optimizer/appendinfo.h +++ b/src/include/optimizer/appendinfo.h @@ -17,9 +17,10 @@ #include "nodes/pathnodes.h" #include "utils/relcache.h" -extern AppendRelInfo *make_append_rel_info(Relation parentrel, - Relation childrel, - Index parentRTindex, Index childRTindex); +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parent, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childtype, + Index childRTindex); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index d0c8f99d0a..2e9ebdc94c 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -265,6 +265,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index 40f70f9f2b..fb370ed80e 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -25,8 +25,8 @@ typedef void (*get_relation_info_hook_type) (PlannerInfo *root, extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; -extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); +extern void get_relation_info(PlannerInfo *root, RangeTblEntry *rte, + RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index a9b2c9026c..27de05ba3e 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -34,7 +34,8 @@ extern Relids get_relids_for_join(Query *query, int joinrelid); /* * prototypes for preptlist.c */ -extern List *preprocess_targetlist(PlannerInfo *root); +extern List *preprocess_targetlist(PlannerInfo *root, + bool inheritance_expanded); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 6bc106831e..1450cef057 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) -- 2.17.2 (Apple Git-113)
v19-0003-Teach-planner-to-only-process-unpruned-partition.patch (application/octet-stream, 6.9K) download From c40035d28641e8a04de3f85519309a7178ae777d Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 15 Oct 2018 10:59:24 +0900 Subject: [PATCH v19 3/4] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/inherit.c | 1 + src/backend/optimizer/util/relnode.c | 3 +++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/pathnodes.h | 4 ++++ 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 6f77d2a0f4..fdbf7689ed 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1452,6 +1452,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index c90b62bea1..9a404eeef0 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6855,7 +6855,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -6863,9 +6865,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -6946,7 +6946,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -6958,7 +6957,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -6968,9 +6969,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 363892466b..5af327b720 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -365,6 +365,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, /* Perform pruning. */ partindexes = prune_append_rel_partitions(parentrel); + parentrel->live_parts = partindexes; /* Must expand PlannerInfo arrays before we can add children. */ if (bms_num_members(partindexes) > 0) diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 09b1b8075a..cfb8077575 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1734,6 +1734,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 472a6cd331..babb7d3406 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -438,29 +438,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 1d36bda039..2571c17f65 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -739,6 +739,10 @@ typedef struct RelOptInfo Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Indexes into part_rels of the non-NULL + * RelOptInfos of unpruned partitions; exists + * to avoid having to iterate over the entire + * part_rels array to filter NULL entries. */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.17.2 (Apple Git-113)
v19-0001-Overhaul-inheritance-update-delete-planning.patch (application/octet-stream, 74.6K) download From fb6700b6a419af45107bb038aa30dafd661a3a00 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 11:18:12 +0900 Subject: [PATCH v19 1/4] Overhaul inheritance update/delete planning With the current method, inheritance_planner, which handles the planning for update/delete commands targeting inheritance trees, repeatedly calls query_planner for each child table. That's inefficient for two reasons: 1. partprune.c cannot be used to perform partition pruning, because it can only be invoked from query_planner. With the current approach, query_planner only sees the individual partitions in the query tree it receives, not the parent partitioned table. This leaves each partition to be pruned using constraint exclusion. (constraint exclusion cannon prune hash partitions which means UPDATE/DELETE don't support hash partition pruning.) 2. Repeated invocation of query_planner results in allocating large amounts of memory, especially if there are many child tables. Also, it involves repeatedly performing the same processing, such as jointree processing. This commit addresses both of the issues by modifying query_planner to handle the inheritance target case directly. Instead of having the caller create separate PlannerInfo's for individual child target relations and pass them to query_planner (actually, via grouping_planner), this teaches query_planner itself to create the child PlannerInfo's by the original parent PlannerInfo. It creates them *after* it has generated enough state in the latter, such as RelOptInfos for various relations, EquivalenceClasses after processing the join tree, and *before* generating the scan and join paths. Scan paths are generated normally, except that Paths of target children are not "appended". Join paths are generated separately for each child target relation, that is, by replacing the parent relation references in the original join tree with the child relation references. Joinrels (RelOptInfos thereof) corresponding to various child target relation are saved in the parent PlannerInfo for further processing. inheritance_planner now calls query_planner just once at the beginning with the original unmodified query, which takes care of generating top-level join rels for the child queries as described above. For each child join rel, it then calls grouping_planner using previously created child PlannerInfo to finish up the paths such that they produce query's top-level target list expanded according to a given child relation's descriptor. grouping_planner()'s interface is modified so that we can pass it what's called a 'planned_rel', a RelOptInfo that already contains the necessary paths needed to produce its output. This removes some existing code in inheritance_planner that dealt with any subquery RTEs in the query. The rationale of that code was that the subquery RTEs may change during each iteration of planning (that is, for different children), so different iterations better use different copies of those RTEs. That was handled by making fresh copies of those RTEs for each iteration of planning which were appended to the range table, accompanied by modifying all expressions and auxiliary structures that referenced the original subquery RTEs to instead reference the copies (that is, change the varnos). This copying meant we would end up adding S * N new entries to the original range table by the time we got to the last unpruned child, where S is the number of subquery RTEs in the original query and N the number of unpruned children. Since with the new code we perform planning just once, I think we don't need this special handling. Actually, there is a regression test output change due to no longer having copies of subquery RTEs (see the diff of partition_join.out file.) Also we no longer need the hack in relation_excluded_by_constraints() involving setting PlannerInfo.inhTargetKind to enable constraint exclusion for target child relation, which is no longer needed. Constraint exclusion now runs during query_planner step described above. Regression test output change in partition_join.out is due to the fact that we no longer create duplicates of subquery RTEs in child range tables. --- doc/src/sgml/ddl.sgml | 17 +- src/backend/nodes/outfuncs.c | 2 +- src/backend/optimizer/path/allpaths.c | 511 ++++++++++++++++--- src/backend/optimizer/path/equivclass.c | 5 +- src/backend/optimizer/path/joinrels.c | 10 +- src/backend/optimizer/plan/createplan.c | 10 - src/backend/optimizer/plan/planmain.c | 41 +- src/backend/optimizer/plan/planner.c | 444 +++++----------- src/backend/optimizer/prep/prepjointree.c | 1 - src/backend/optimizer/util/appendinfo.c | 160 +++++- src/backend/optimizer/util/inherit.c | 107 ++++ src/backend/optimizer/util/plancat.c | 73 +-- src/include/nodes/pathnodes.h | 39 +- src/include/optimizer/inherit.h | 1 + src/include/optimizer/paths.h | 3 + src/test/regress/expected/partition_join.out | 4 +- 16 files changed, 936 insertions(+), 492 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 85e4358988..1bb5ca6b08 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -4438,25 +4438,12 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; </para> <note> - <para> - Currently, pruning of partitions during the planning of an - <command>UPDATE</command> or <command>DELETE</command> command is - implemented using the constraint exclusion method (however, it is - controlled by the <literal>enable_partition_pruning</literal> rather than - <literal>constraint_exclusion</literal>) &mdash; see the following section - for details and caveats that apply. - </para> - <para> Execution-time partition pruning currently only occurs for the <literal>Append</literal> and <literal>MergeAppend</literal> node types. It is not yet implemented for the <literal>ModifyTable</literal> node - type. - </para> - - <para> - Both of these behaviors are likely to be changed in a future release - of <productname>PostgreSQL</productname>. + type, but that is likely to be changed in a future release of + <productname>PostgreSQL</productname>. </para> </note> </sect2> diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 9d44e3e4c6..e7a65985cf 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2210,7 +2210,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); WRITE_UINT_FIELD(qual_security_level); - WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind); WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasHavingQual); @@ -2220,6 +2219,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_BITMAPSET_FIELD(curOuterRels); WRITE_NODE_FIELD(curOuterParams); WRITE_BOOL_FIELD(partColsUpdated); + WRITE_BOOL_FIELD(inherited_update); } static void diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 55b871c02c..f07db81a80 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -39,6 +39,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" @@ -92,8 +93,12 @@ static void set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); +static void set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte); static void set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); +static void set_inherit_target_rel_pathlists(PlannerInfo *root, + RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, @@ -122,6 +127,8 @@ static void set_result_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); +static RelOptInfo *inheritance_make_rel_from_joinlist(PlannerInfo *root, + List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, pushdown_safety_info *safetyInfo); static bool recurse_pushdown_safe(Node *setOp, Query *topquery, @@ -155,27 +162,6 @@ make_one_rel(PlannerInfo *root, List *joinlist) Index rti; double total_pages; - /* - * Construct the all_baserels Relids set. - */ - root->all_baserels = NULL; - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - - /* there may be empty slots corresponding to non-baserel RTEs */ - if (brel == NULL) - continue; - - Assert(brel->relid == rti); /* sanity check on array */ - - /* ignore RTEs that are "other rels" */ - if (brel->reloptkind != RELOPT_BASEREL) - continue; - - root->all_baserels = bms_add_member(root->all_baserels, brel->relid); - } - /* Mark base rels as to whether we care about fast-start plans */ set_base_rel_consider_startup(root); @@ -223,13 +209,34 @@ make_one_rel(PlannerInfo *root, List *joinlist) /* * Generate access paths for the entire join tree. + * + * For UPDATE/DELETE on an inheritance parent, join paths should be + * generated for each child result rel separately. */ - rel = make_rel_from_joinlist(root, joinlist); + if (root->inherited_update) + { + /* + * RelOptInfo corresponding to the query's original target relation + * is returned. Join paths (if any) are attached to child joinrels, + * not this rel. Also, this rel's sole path (ModifyTable) will be set + * by inheritance_planner later, so we can't check its paths yet. + */ + rel = inheritance_make_rel_from_joinlist(root, joinlist); + } + else + { + rel = make_rel_from_joinlist(root, joinlist); - /* - * The result should join all and only the query's base rels. - */ - Assert(bms_equal(rel->relids, root->all_baserels)); + /* + * The result should join all and only the query's base rels. + */ + Assert(bms_equal(rel->relids, root->all_baserels)); + + /* Check that we got at least one usable path */ + if (!rel || !rel->cheapest_total_path || + rel->cheapest_total_path->param_info != NULL) + elog(ERROR, "failed to construct the join relation"); + } return rel; } @@ -313,9 +320,10 @@ set_base_rel_sizes(PlannerInfo *root) * If parallelism is allowable for this query in general, see whether * it's allowable for this rel in particular. We have to do this * before set_rel_size(), because (a) if this rel is an inheritance - * parent, set_append_rel_size() will use and perhaps change the rel's - * consider_parallel flag, and (b) for some RTE types, set_rel_size() - * goes ahead and makes paths immediately. + * parent, set_append_rel_size() or set_inherit_target_rel_sizes() + * will use and perhaps change the rel's consider_parallel flag, and + * (b) for some RTE types, set_rel_size() goes ahead and makes paths + * immediately. */ if (root->glob->parallelModeOK) set_rel_consider_parallel(root, rel, rte); @@ -379,8 +387,15 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_size(root, rel, rti, rte); + /* + * If it's a target relation, set the sizes of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (rti == root->parse->resultRelation) + set_inherit_target_rel_sizes(root, rel, rti, rte); + else + set_append_rel_size(root, rel, rti, rte); } else { @@ -469,14 +484,26 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { + bool inherited_update = false; + if (IS_DUMMY_REL(rel)) { /* We already proved the relation empty, so nothing more to do */ } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_pathlist(root, rel, rti, rte); + /* + * If it's a target relation, set the pathlists of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (root->inherited_update && root->parse->resultRelation == rti) + { + inherited_update = true; + set_inherit_target_rel_pathlists(root, rel, rti, rte); + } + else + set_append_rel_pathlist(root, rel, rti, rte); } else { @@ -554,8 +581,12 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, if (set_rel_pathlist_hook) (*set_rel_pathlist_hook) (root, rel, rti, rte); - /* Now find the cheapest of the paths for this rel */ - set_cheapest(rel); + /* + * Now find the cheapest of the paths for this rel, unless it's an + * inheritance parent and this is an update/delete operation. + */ + if (!inherited_update) + set_cheapest(rel); #ifdef OPTIMIZER_DEBUG debug_print_rel(root, rel); @@ -920,6 +951,225 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) rel->fdwroutine->GetForeignPaths(root, rel, rte->relid); } +/* + * set_inherit_target_rel_sizes + * Set size estimates for the child target relations + * + * The passed-in rel represents the target relation of the query that is + * known to have inheritance children. This is very much like + * set_append_rel_size, except it doesn't set the size estimates for the + * passed-in rel itself, because we don't need to "append" the children + * in this case. + */ +static void +set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + bool has_live_children = false; + ListCell *l; + Relids live_children = NULL; + bool did_pruning = false; + + /* Guard against stack overflow due to overly deep inheritance tree. */ + check_stack_depth(); + + Assert(IS_SIMPLE_REL(rel)); + + /* + * If the partitioned relation has any baserestrictinfo quals then we + * attempt to use these quals to prune away partitions that cannot + * possibly contain any tuples matching these quals. In this case we'll + * store the relids of all partitions which could possibly contain a + * matching tuple, and skip anything else in the loop below. + */ + if (enable_partition_pruning && + rte->relkind == RELKIND_PARTITIONED_TABLE && + rel->baserestrictinfo != NIL) + { + live_children = prune_append_rel_partitions(rel); + did_pruning = true; + } + + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + PlannerInfo *subroot; + ListCell *lc; + List *translated_exprs, + *child_target_exprs; + bool childpruned; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + + /* + * The child rel's RelOptInfo was already created during + * add_base_rels_to_query. + */ + childrel = find_base_rel(root, childRTindex); + Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); + + childpruned = (did_pruning && + !bms_is_member(appinfo->child_relid, live_children)); + + /* + * Unless the child is pruned, 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 baserestrictinfo quals. + */ + if (childpruned || + !apply_child_basequals(root, rel, childrel, childRTE, appinfo) || + relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* This partition needn't be scanned; skip it. */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Add missing Vars to child's reltarget. + * + * add_inherit_target_child_root() would've added only those that are + * needed to be present in the top-level tlist (or ones that + * preprocess_targetlist thinks are needed to be in the tlist.) We + * may need other attributes such as those contained in WHERE clauses, + * which are already computed for the parent during + * deconstruct_jointree processing of the original query (child's + * query never goes through deconstruct_jointree.) + */ + translated_exprs = (List *) + adjust_appendrel_attrs(root, + (Node *) rel->reltarget->exprs, + 1, &appinfo); + child_target_exprs = childrel->reltarget->exprs; + foreach(lc, translated_exprs) + { + Expr *expr = lfirst(lc); + + if (!list_member(child_target_exprs, expr)) + child_target_exprs = lappend(child_target_exprs, expr); + } + + subroot = root->inh_target_child_roots[childRTindex]; + + /* + * Also, We have to find any ECs containing parent's expressions and + * *replace* them with their copies containing child expressions. + */ + if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) + subroot->eq_classes = (List *) + adjust_appendrel_attrs(root, + (Node *) root->eq_classes, + 1, &appinfo); + childrel->has_eclass_joins = rel->has_eclass_joins; + + /* Translate join quals. */ + childrel->joininfo = (List *) + adjust_appendrel_attrs(subroot, + (Node *) rel->joininfo, + 1, &appinfo); + + /* + * Compute the child's size using possibly modified subroot. + */ + set_rel_size(subroot, childrel, childRTindex, childRTE); + + /* If the child itself is partitioned it may turn into a dummy rel. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* We have at least one live child. */ + has_live_children = true; + + Assert(childrel->rows > 0); + } + + if (has_live_children) + { + /* + * Set a non-zero value here to cope with the caller's requirement + * that non-dummy relations are actually not empty. We don't try to + * be accurate here, because we're not going to create a path that + * combines the children outputs. + */ + rel->rows = 1; + } + else + { + /* + * All children were excluded by constraints, so mark the relation + * as dummy. We must do this in this phase so that the rel's + * dummy-ness is visible when we generate paths for other rels. + */ + set_dummy_rel_pathlist(rel); + } +} + +/* + * set_inherit_target_rel_pathlists + * Build access paths for the child target relations + * + * Similar to set_append_rel_pathlist, except that we build paths of the + * children, but don't build an Append path. + */ +static void +set_inherit_target_rel_pathlists(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + ListCell *l; + + /* Nothing to do if all the children were excluded. */ + if (IS_DUMMY_REL(rel)) + return; + + /* Generate access paths for each of the children of passed-in rel */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + PlannerInfo *subroot; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + /* Re-locate the child RTE and RelOptInfo */ + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + childrel = root->simple_rel_array[childRTindex]; + subroot = root->inh_target_child_roots[childRTindex]; + /* Transfer the value from main root to subroot. */ + subroot->total_table_pages = root->total_table_pages; + + /* + * If set_append_rel_size() decided the parent appendrel was + * parallel-unsafe at some point after visiting this child rel, we + * need to propagate the unsafety marking down to the child, so that + * we don't generate useless partial paths for it. + */ + if (!rel->consider_parallel) + childrel->consider_parallel = false; + + /* + * Compute the child's access paths. + */ + set_rel_pathlist(subroot, childrel, childRTindex, childRTE); + } +} + /* * set_append_rel_size * Set size estimates for a simple "append relation" @@ -1015,6 +1265,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, RelOptInfo *childrel; ListCell *parentvars; ListCell *childvars; + bool childpruned; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1030,36 +1281,20 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - + childpruned = (did_pruning && + !bms_is_member(appinfo->child_relid, live_children)); /* - * 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 - * baserestrictinfo quals. + * Unless the child is pruned, 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 baserestrictinfo quals. */ - if (!apply_child_basequals(root, rel, childrel, childRTE, appinfo)) + if (childpruned || + !apply_child_basequals(root, rel, childrel, childRTE, appinfo) || + relation_excluded_by_constraints(root, childrel, childRTE)) { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* - * This child need not be scanned, so we can omit it from the - * appendrel. - */ + /* This partition needn't be scanned; skip it. */ set_dummy_rel_pathlist(childrel); continue; } @@ -2562,6 +2797,158 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows) } } +/* + * inheritance_make_rel_from_joinlist + * Perform join planning for all non-dummy leaf inheritance children + * in their role as an UPDATE/DELETE query's target relation + * + * If a child relation is a partitioned table, its children are processed in + * turn by recursively calling this function. + */ +static RelOptInfo * +inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) +{ + Index resultRelation = root->parse->resultRelation; + RelOptInfo *resultrel; + ListCell *lc; +#ifdef USE_ASSERT_CHECKING + Relids all_baserels; +#endif + + /* For UPDATE/DELETE queries, the top parent can only ever be a table. */ + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + Assert(planner_rt_fetch(resultRelation, root)->rtekind == RTE_RELATION); + resultrel = find_base_rel(root, resultRelation); + + /* Nothing to do. */ + if (IS_DUMMY_REL(resultrel)) + return resultrel; + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + PlannerInfo *subroot; + RelOptInfo *childrel; + RelOptInfo *childjoinrel; + List *translated_joinlist; + + if (appinfo->parent_relid != resultRelation) + continue; + + childrel = find_base_rel(root, appinfo->child_relid); + + /* Ignore excluded/pruned children. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* Add this child. */ + root->inh_target_child_rels = lappend_int(root->inh_target_child_rels, + appinfo->child_relid); + + /* Perform join planning with child subroot. */ + subroot = root->inh_target_child_roots[appinfo->child_relid]; + Assert(subroot->parse->resultRelation > 0); + + /* + * Modify joinlist such that relations joined to the top parent rel + * appear to be joined to the child rel instead. Do the same for + * any SpecialJoinInfo structs. + */ + translated_joinlist = (List *) + adjust_appendrel_attrs(subroot, + (Node *) joinlist, + 1, &appinfo); + subroot->join_info_list = (List *) + adjust_appendrel_attrs(subroot, + (Node *) root->join_info_list, + 1, &appinfo); + + /* + * Sub-partitioned tables have to be processed recursively using the + * translated subroot as the parent, because AppendRelInfos link + * sub-partitions to their immediate parents, not the root partitioned + * table. + */ + if (childrel->part_scheme != NULL) + { + /* + * inheritance_make_rel_from_joinlist() return the target relation + * RelOptInfo. + */ + childrel = + inheritance_make_rel_from_joinlist(subroot, + translated_joinlist); + + /* + * Add this child relation as a placeholder in the parent root's + * inh_target_child_path_rels so that inheritance_planner sees + * same number of entries in it as inh_target_child_rels. + */ + root->inh_target_child_path_rels = + lappend(root->inh_target_child_path_rels, childrel); + + /* + * Also propagate this child's own children into the parent's + * list. + */ + if (subroot->inh_target_child_rels != NIL) + { + root->inh_target_child_rels = + list_concat(root->inh_target_child_rels, + subroot->inh_target_child_rels); + root->inh_target_child_path_rels = + list_concat(root->inh_target_child_path_rels, + subroot->inh_target_child_path_rels); + } + continue; + } + + /* + * Since we added the child rel directly into the join tree, we must + * modify it to be a "base" rel instead of an "other" rel, which the + * join planning code expects the relations being joined to be. + */ + childrel->reloptkind = RELOPT_BASEREL; + + Assert(subroot->join_rel_list == NIL); + Assert(subroot->join_rel_hash == NULL); + + /* Perform join planning and save the resulting RelOptInfo. */ + childjoinrel = make_rel_from_joinlist(subroot, translated_joinlist); + + /* Check that we got at least one usable path */ + if (!childjoinrel || !childjoinrel->cheapest_total_path || + childjoinrel->cheapest_total_path->param_info != NULL) + elog(ERROR, "failed to construct the child join relation"); + + /* + * Remember the paths of child target rel. inheritance_planner will + * perform the remaining steps of planning for each child relation + * separately. Specifically, it will call grouping_planner on every + * RelOptInfo contained in the inh_target_child_rels list, each of + * which represents the source of tuples to be modified for a given + * target child rel. + */ + root->inh_target_child_path_rels = + lappend(root->inh_target_child_path_rels, childjoinrel); +#ifdef USE_ASSERT_CHECKING + /* + * The following implements essentially the same Assert as in + * make_one_rel, our caller. + */ + all_baserels = bms_copy(root->all_baserels); + all_baserels = bms_del_member(all_baserels, + root->parse->resultRelation); + all_baserels = bms_add_member(all_baserels, + subroot->parse->resultRelation); + Assert(bms_equal(childjoinrel->relids, all_baserels)); +#endif + } + + return resultrel; +} + /* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 3454f12912..332dc34276 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -32,9 +32,6 @@ #include "utils/lsyscache.h" -static EquivalenceMember *add_eq_member(EquivalenceClass *ec, - Expr *expr, Relids relids, Relids nullable_relids, - bool is_child, Oid datatype); static void generate_base_implied_equalities_const(PlannerInfo *root, EquivalenceClass *ec); static void generate_base_implied_equalities_no_const(PlannerInfo *root, @@ -540,7 +537,7 @@ canonicalize_ec_expression(Expr *expr, Oid req_type, Oid req_collation) /* * add_eq_member - build a new EquivalenceMember and add it to an EC */ -static EquivalenceMember * +EquivalenceMember * add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids, Relids nullable_relids, bool is_child, Oid datatype) { diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index dfbbfdac6d..a02321486f 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1462,8 +1462,14 @@ update_child_rel_info(PlannerInfo *root, (Node *) rel->reltarget->exprs, 1, &appinfo); - /* Make child entries in the EquivalenceClass as well */ - if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) + /* + * Make child entries in the EquivalenceClass as well. If the childrel + * appears to be a dummy one (one built by build_dummy_partition_rel()), + * no need to make any new entries, because anything that'd need those + * can instead use the parent's (rel). + */ + if (childrel->relid != rel->relid && + (rel->has_eclass_joins || has_useful_pathkeys(root, rel))) add_child_rel_equivalences(root, appinfo, rel, childrel); childrel->has_eclass_joins = rel->has_eclass_joins; } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 1b4f7db649..496c560cad 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -2049,12 +2049,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) /* * During setrefs.c, we'll need the grouping_map to fix up the cols lists * in GroupingFunc nodes. Save it for setrefs.c to use. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be grouping in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->grouping_map == NULL); root->grouping_map = grouping_map; @@ -2211,12 +2206,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) * with InitPlan output params. (We can't just do that locally in the * MinMaxAgg node, because path nodes above here may have Agg references * as well.) Save the mmaggregates list to tell setrefs.c to do that. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be aggregates in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->minmax_aggs == NIL); root->minmax_aggs = best_path->mmaggregates; diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 3cedd01c98..acb1461624 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -29,7 +29,7 @@ #include "optimizer/paths.h" #include "optimizer/placeholder.h" #include "optimizer/planmain.h" - +#include "optimizer/prep.h" /* * query_planner @@ -60,6 +60,7 @@ query_planner(PlannerInfo *root, List *tlist, Query *parse = root->parse; List *joinlist; RelOptInfo *final_rel; + Index rti; /* * Init planner lists to empty. @@ -259,15 +260,43 @@ query_planner(PlannerInfo *root, List *tlist, */ extract_restriction_or_clauses(root); + /* + * Construct the all_baserels Relids set. + */ + root->all_baserels = NULL; + for (rti = 1; rti < root->simple_rel_array_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + Assert(brel->relid == rti); /* sanity check on array */ + + /* ignore RTEs that are "other rels" */ + if (brel->reloptkind != RELOPT_BASEREL) + continue; + + root->all_baserels = bms_add_member(root->all_baserels, brel->relid); + } + + /* + * Add child subroots needed to use during planning for individual child + * targets + */ + if (root->inherited_update) + { + root->inh_target_child_roots = (PlannerInfo **) + palloc0(root->simple_rel_array_size * + sizeof(PlannerInfo *)); + add_inherited_target_child_roots(root); + } + /* * Ready to do the primary planning. */ final_rel = make_one_rel(root, joinlist); - /* Check that we got at least one usable path */ - if (!final_rel || !final_rel->cheapest_total_path || - final_rel->cheapest_total_path->param_info != NULL) - elog(ERROR, "failed to construct the join relation"); - return final_rel; } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index b2239728cf..71aa8dc72a 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -36,6 +36,7 @@ #include "lib/knapsack.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "nodes/pathnodes.h" #ifdef OPTIMIZER_DEBUG #include "nodes/print.h" #endif @@ -131,7 +132,7 @@ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static void inheritance_planner(PlannerInfo *root); static void grouping_planner(PlannerInfo *root, bool inheritance_update, - double tuple_fraction); + RelOptInfo *planned_rel, double tuple_fraction); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, int *tleref_to_colnum_map); @@ -636,7 +637,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->grouping_map = NULL; root->minmax_aggs = NIL; root->qual_security_level = 0; - root->inhTargetKind = INHKIND_NONE; root->hasRecursion = hasRecursion; if (hasRecursion) root->wt_param_id = assign_special_exec_param(root); @@ -737,6 +737,13 @@ subquery_planner(PlannerGlobal *glob, Query *parse, */ expand_inherited_tables(root); + /* + * Now that we have figured out "actual" inheritance situation of the + * relations, set whether the query is an inherited UPDATE/DELETE. + */ + root->inherited_update = (parse->resultRelation && + rt_fetch(parse->resultRelation, parse->rtable)->inh); + /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to @@ -997,7 +1004,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh) inheritance_planner(root); else - grouping_planner(root, false, tuple_fraction); + grouping_planner(root, false, NULL, tuple_fraction); /* * Capture the set of outer-level param IDs we have access to, for use in @@ -1173,13 +1180,41 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr) * inheritance set. * * We have to handle this case differently from cases where a source relation - * is an inheritance set. Source inheritance is expanded at the bottom of the - * plan tree (see allpaths.c), but target inheritance has to be expanded at - * the top. The reason is that for UPDATE, each target relation needs a - * different targetlist matching its own column set. Fortunately, + * is an inheritance set, where outputs of all the children are combined and + * combined output consumed using the source relation's (parent's) column set. + * For UPDATE, each target relation, where the query's output will go, needs + * a different targetlist matching its own column set. So, we will need to + * translate the query such that it produces the desired output required by + * each of the child target relations and plan each separately. Fortunately, * the UPDATE/DELETE target can never be the nullable side of an outer join, * so it's OK to generate the plan this way. * + * Planning steps that need to be performed separately for each child and + * any preliminary processing needed for that are handled by the following + * sub-routines of query_planner: + * + * add_inherit_target_roots - this creates copies of PlannerInfo for each + * child after query_planner has finished processing the join tree and + * creating EquivalenceClasses, so that working state of planning need + * not be created afresh for each child, especially the various arrays + * + * set_inherit_target_rel_sizes - this sets size estimates for child + * relations, replace the parent EC members by corresponding child ones + * in their respective subroots + * + * set_inherit_target_rel_pathlists - this creates Paths for scanning + * individual child relations + * + * inherit_make_rel_from_joinlist - this translates the jointree, replacing + * the target relation in the original jointree (the root parent) by + * individual child target relations and performs join planning on the + * resulting join tree, saving the RelOptInfos of resulting join relations + * into the top-level PlannerInfo + * + * Finally, here we apply grouping_planner to each child join relation + * produced by set_inherit_target_rel_pathlists to so that its path produces + * the desired targetlist. + * * Returns nothing; the useful output is in the Paths we attach to * the (UPPERREL_FINAL, NULL) upperrel stored in *root. * @@ -1191,14 +1226,8 @@ inheritance_planner(PlannerInfo *root) { Query *parse = root->parse; int top_parentRTindex = parse->resultRelation; - Bitmapset *subqueryRTindexes; - Bitmapset *modifiableARIindexes; int nominalRelation = -1; Index rootRelation = 0; - List *final_rtable = NIL; - int save_rel_array_size = 0; - RelOptInfo **save_rel_array = NULL; - AppendRelInfo **save_append_rel_array = NULL; List *subpaths = NIL; List *subroots = NIL; List *resultRelations = NIL; @@ -1206,70 +1235,49 @@ inheritance_planner(PlannerInfo *root) List *returningLists = NIL; List *rowMarks; RelOptInfo *final_rel; - ListCell *lc; - Index rti; + ListCell *lc1, + *lc2; RangeTblEntry *parent_rte; - PlannerInfo *parent_root; - Query *parent_parse; - Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); - PlannerInfo **parent_roots = NULL; + List *tlist; + standard_qp_extra qp_extra; + RelOptInfo *planned_rel; + /* Inheritance is never used for insert. */ Assert(parse->commandType != CMD_INSERT); /* - * We generate a modified instance of the original Query for each target - * relation, plan that, and put all the plans into a list that will be - * controlled by a single ModifyTable node. All the instances share the - * same rangetable, but each instance must have its own set of subquery - * RTEs within the finished rangetable because (1) they are likely to get - * scribbled on during planning, and (2) it's not inconceivable that - * subqueries could get planned differently in different cases. We need - * not create duplicate copies of other RTE kinds, in particular not the - * target relations, because they don't have either of those issues. Not - * having to duplicate the target relations is important because doing so - * (1) would result in a rangetable of length O(N^2) for N targets, with - * at least O(N^3) work expended here; and (2) would greatly complicate - * management of the rowMarks list. + * Let query_planner generate the access paths for the query for each + * target child relation. * - * To begin with, generate a bitmapset of the relids of the subquery RTEs. + * First, save the unexpanded version of the query's targetlist. + * create_inherit_target_child_root will use it as base when expanding + * it for a given child relation as the query's target relation instead + * of the parent. */ - subqueryRTindexes = NULL; - rti = 1; - foreach(lc, parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); + root->unexpanded_tlist = list_copy(root->parse->targetList); - if (rte->rtekind == RTE_SUBQUERY) - subqueryRTindexes = bms_add_member(subqueryRTindexes, rti); - rti++; - } + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root); + root->processed_tlist = tlist; + qp_extra.tlist = tlist; + qp_extra.activeWindows = NIL; + qp_extra.groupClause = NIL; + planned_rel = query_planner(root, tlist, standard_qp_callback, &qp_extra); /* - * Next, we want to identify which AppendRelInfo items contain references - * to any of the aforesaid subquery RTEs. These items will need to be - * copied and modified to adjust their subquery references; whereas the - * other ones need not be touched. It's worth being tense over this - * because we can usually avoid processing most of the AppendRelInfo - * items, thereby saving O(N^2) space and time when the target is a large - * inheritance tree. We can identify AppendRelInfo items by their - * child_relid, since that should be unique within the list. + * If it turned out during query planning that all the children are dummy + * (pruned or excluded by constraints), no need to do the steps below. + * Let grouping_planner finish up the final path. */ - modifiableARIindexes = NULL; - if (subqueryRTindexes != NULL) + if (IS_DUMMY_REL(planned_rel)) { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - - if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) || - bms_is_member(appinfo->child_relid, subqueryRTindexes) || - bms_overlap(pull_varnos((Node *) appinfo->translated_vars), - subqueryRTindexes)) - modifiableARIindexes = bms_add_member(modifiableARIindexes, - appinfo->child_relid); - } + grouping_planner(root, false, planned_rel, 0.0); + return; } + Assert(planned_rel->relid == top_parentRTindex); + Assert(planned_rel->reloptkind == RELOPT_BASEREL); + /* * If the parent RTE is a partitioned table, we should use that as the * nominal target relation, because the RTEs added for partitioned tables @@ -1277,7 +1285,7 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); + parent_rte = planner_rt_fetch(top_parentRTindex, root); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1285,95 +1293,39 @@ inheritance_planner(PlannerInfo *root) } /* - * The PlannerInfo for each child is obtained by translating the relevant - * members of the PlannerInfo for its immediate parent, which we find - * using the parent_relid in its AppendRelInfo. We save the PlannerInfo - * for each parent in an array indexed by relid for fast retrieval. Since - * the maximum number of parents is limited by the number of RTEs in the - * query, we use that number to allocate the array. An extra entry is - * needed since relids start from 1. + * Get on with finalizing the path for each child target relation by + * calling grouping_planner on its joinrel. */ - parent_roots = (PlannerInfo **) palloc0((list_length(parse->rtable) + 1) * - sizeof(PlannerInfo *)); - parent_roots[top_parentRTindex] = root; - - /* - * And now we can get on with generating a plan for each child table. - */ - foreach(lc, root->append_rel_list) + forboth(lc1, root->inh_target_child_rels, + lc2, root->inh_target_child_path_rels) { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - PlannerInfo *subroot; + Index childRTindex = lfirst_int(lc1); + RelOptInfo *childjoinrel = lfirst(lc2); + PlannerInfo *subroot = root->inh_target_child_roots[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; RangeTblEntry *child_rte; RelOptInfo *sub_final_rel; Path *subpath; - /* append_rel_list contains all append rels; ignore others */ - if (!bms_is_member(appinfo->parent_relid, parent_relids)) - continue; - - /* - * expand_inherited_rtentry() always processes a parent before any of - * that parent's children, so the parent_root for this relation should - * already be available. - */ - parent_root = parent_roots[appinfo->parent_relid]; - Assert(parent_root != NULL); - parent_parse = parent_root->parse; - - /* - * We need a working copy of the PlannerInfo so that we can control - * propagation of information back to the main copy. - */ - subroot = makeNode(PlannerInfo); - memcpy(subroot, parent_root, sizeof(PlannerInfo)); - - /* - * Generate modified query with this rel as target. We first apply - * adjust_appendrel_attrs, which copies the Query and changes - * references to the parent RTE to refer to the current child RTE, - * then fool around with subquery RTEs. - */ - subroot->parse = (Query *) - adjust_appendrel_attrs(parent_root, - (Node *) parent_parse, - 1, &appinfo); + Assert(subroot != NULL); + Assert(subroot->parse->resultRelation == childRTindex); /* * If there are securityQuals attached to the parent, move them to the * child rel (they've already been transformed properly for that). */ - parent_rte = rt_fetch(appinfo->parent_relid, subroot->parse->rtable); - child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable); + parent_rte = planner_rt_fetch(appinfo->parent_relid, subroot); + child_rte = planner_rt_fetch(appinfo->child_relid, subroot); child_rte->securityQuals = parent_rte->securityQuals; parent_rte->securityQuals = NIL; /* - * Mark whether we're planning a query to a partitioned table or an - * inheritance parent. + * Ignore a partitioned child. Instead, the paths of its children will + * be added to subpaths. */ - subroot->inhTargetKind = - (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED; - - /* - * If this child is further partitioned, remember it as a parent. - * Since a partitioned table does not have any data, we don't need to - * create a plan for it, and we can stop processing it here. We do, - * however, need to remember its modified PlannerInfo for use when - * processing its children, since we'll update their varnos based on - * the delta from immediate parent to child, not from top to child. - * - * Note: a very non-obvious point is that we have not yet added - * duplicate subquery RTEs to the subroot's rtable. We mustn't, - * because then its children would have two sets of duplicates, - * confusing matters. - */ - if (child_rte->inh) + if (childjoinrel->part_scheme) { - Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE); - parent_relids = bms_add_member(parent_relids, appinfo->child_relid); - parent_roots[appinfo->child_relid] = subroot; - + Assert(child_rte->inh); continue; } @@ -1401,111 +1353,8 @@ inheritance_planner(PlannerInfo *root) if (nominalRelation < 0) nominalRelation = appinfo->child_relid; - /* - * The rowMarks list might contain references to subquery RTEs, so - * make a copy that we can apply ChangeVarNodes to. (Fortunately, the - * executor doesn't need to see the modified copies --- we can just - * pass it the original rowMarks list.) - */ - subroot->rowMarks = copyObject(parent_root->rowMarks); - - /* - * The append_rel_list likewise might contain references to subquery - * RTEs (if any subqueries were flattenable UNION ALLs). So prepare - * to apply ChangeVarNodes to that, too. As explained above, we only - * want to copy items that actually contain such references; the rest - * can just get linked into the subroot's append_rel_list. - * - * If we know there are no such references, we can just use the outer - * append_rel_list unmodified. - */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - subroot->append_rel_list = NIL; - foreach(lc2, parent_root->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, modifiableARIindexes)) - appinfo2 = copyObject(appinfo2); - - subroot->append_rel_list = lappend(subroot->append_rel_list, - appinfo2); - } - } - - /* - * Add placeholders to the child Query's rangetable list to fill the - * RT indexes already reserved for subqueries in previous children. - * These won't be referenced, so there's no need to make them very - * valid-looking. - */ - while (list_length(subroot->parse->rtable) < list_length(final_rtable)) - subroot->parse->rtable = lappend(subroot->parse->rtable, - makeNode(RangeTblEntry)); - - /* - * If this isn't the first child Query, generate duplicates of all - * subquery RTEs, and adjust Var numbering to reference the - * duplicates. To simplify the loop logic, we scan the original rtable - * not the copy just made by adjust_appendrel_attrs; that should be OK - * since subquery RTEs couldn't contain any references to the target - * rel. - */ - if (final_rtable != NIL && subqueryRTindexes != NULL) - { - ListCell *lr; - - rti = 1; - foreach(lr, parent_parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lr); - - if (bms_is_member(rti, subqueryRTindexes)) - { - Index newrti; - - /* - * The RTE can't contain any references to its own RT - * index, except in its securityQuals, so we can save a - * few cycles by applying ChangeVarNodes to the rest of - * the rangetable before we append the RTE to it. - */ - newrti = list_length(subroot->parse->rtable) + 1; - ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); - ChangeVarNodes((Node *) subroot->rowMarks, rti, newrti, 0); - /* Skip processing unchanging parts of append_rel_list */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - foreach(lc2, subroot->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, - modifiableARIindexes)) - ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); - } - } - rte = copyObject(rte); - ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); - subroot->parse->rtable = lappend(subroot->parse->rtable, - rte); - } - rti++; - } - } - - /* There shouldn't be any OJ info to translate, as yet */ - Assert(subroot->join_info_list == NIL); - /* and we haven't created PlaceHolderInfos, either */ - Assert(subroot->placeholder_list == NIL); - - /* Generate Path(s) for accessing this result relation */ - grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ ); + /* Apply the top-level targetlist to childjoinrel's path. */ + grouping_planner(subroot, true, childjoinrel, 0.0); /* * Select cheapest path in case there's more than one. We always run @@ -1517,45 +1366,10 @@ inheritance_planner(PlannerInfo *root) subpath = sub_final_rel->cheapest_total_path; /* - * If this child rel was excluded by constraint exclusion, exclude it - * from the result plan. + * child rel cannot be empty, or inheritance_make_rel_from_joinlist + * wouldn't have put it in the list to begin with. */ - if (IS_DUMMY_PATH(subpath)) - continue; - - /* - * If this is the first non-excluded child, its post-planning rtable - * becomes the initial contents of final_rtable; otherwise, append - * just its modified subquery RTEs to final_rtable. - */ - if (final_rtable == NIL) - final_rtable = subroot->parse->rtable; - else - final_rtable = list_concat(final_rtable, - list_copy_tail(subroot->parse->rtable, - list_length(final_rtable))); - - /* - * We need to collect all the RelOptInfos from all child plans into - * the main PlannerInfo, since setrefs.c will need them. We use the - * last child's simple_rel_array (previous ones are too short), so we - * have to propagate forward the RelOptInfos that were already built - * in previous children. - */ - Assert(subroot->simple_rel_array_size >= save_rel_array_size); - for (rti = 1; rti < save_rel_array_size; rti++) - { - RelOptInfo *brel = save_rel_array[rti]; - - if (brel) - subroot->simple_rel_array[rti] = brel; - } - save_rel_array_size = subroot->simple_rel_array_size; - save_rel_array = subroot->simple_rel_array; - save_append_rel_array = subroot->append_rel_array; - - /* Make sure any initplans from this rel get into the outer list */ - root->init_plans = subroot->init_plans; + Assert(!IS_DUMMY_PATH(subpath)); /* Build list of sub-paths */ subpaths = lappend(subpaths, subpath); @@ -1586,36 +1400,6 @@ inheritance_planner(PlannerInfo *root) * to get control here. */ - /* - * If we managed to exclude every child rel, return a dummy plan; it - * doesn't even need a ModifyTable node. - */ - if (subpaths == NIL) - { - set_dummy_rel_pathlist(final_rel); - return; - } - - /* - * Put back the final adjusted rtable into the master copy of the Query. - * (We mustn't do this if we found no non-excluded children.) - */ - parse->rtable = final_rtable; - root->simple_rel_array_size = save_rel_array_size; - root->simple_rel_array = save_rel_array; - root->append_rel_array = save_append_rel_array; - - /* Must reconstruct master's simple_rte_array, too */ - root->simple_rte_array = (RangeTblEntry **) - palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *)); - rti = 1; - foreach(lc, final_rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - root->simple_rte_array[rti++] = rte; - } - /* * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will * have dealt with fetching non-locked marked rows, else we need to have @@ -1656,6 +1440,12 @@ inheritance_planner(PlannerInfo *root) * (inheritance_planner will create a single ModifyTable node covering all the * target tables.) * + * If non-NULL, planned_rel is a RelOptInfo containing paths for the query's + * top-level joinrel, which the caller produced by itself. In that case, this + * function only needs to adjust the targetlist of its cheapest_total_path. + * The only caller that may pass such a RelOptInfo currently is + * inheritance_planner. + * * tuple_fraction is the fraction of tuples we expect will be retrieved. * tuple_fraction is interpreted as follows: * 0: expect all tuples to be retrieved (normal case) @@ -1674,6 +1464,7 @@ inheritance_planner(PlannerInfo *root) */ static void grouping_planner(PlannerInfo *root, bool inheritance_update, + RelOptInfo *planned_rel, double tuple_fraction) { Query *parse = root->parse; @@ -1686,7 +1477,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, List *final_targets; List *final_targets_contain_srfs; bool final_target_parallel_safe; - RelOptInfo *current_rel; + RelOptInfo *current_rel = planned_rel; RelOptInfo *final_rel; ListCell *lc; @@ -1726,6 +1517,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * special work for recursive unions is the responsibility of * plan_set_operations. */ + Assert(current_rel == NULL); current_rel = plan_set_operations(root); /* @@ -1815,17 +1607,26 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, parse->groupClause = preprocess_groupclause(root, NIL); } - /* Preprocess targetlist */ - tlist = preprocess_targetlist(root); - /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation so - * that we can transfer its decoration (resnames etc) to the topmost - * tlist of the finished Plan. + * Preprocess targetlist, if needed. If the caller has already done + * query planning, root->processed_tlist already contains the desired + * targetlist. */ - root->processed_tlist = tlist; + if (planned_rel == NULL) + { + tlist = preprocess_targetlist(root); + + /* + * We are now done hacking up the query's targetlist. Most of the + * remaining planning work will be done with the PathTarget + * representation of tlists, but save aside the full representation + * so that we can transfer its decoration (resnames etc) to the + * topmost tlist of the finished Plan. + */ + root->processed_tlist = tlist; + } + else + tlist = root->processed_tlist; /* * Collect statistics about aggregates for estimating costs, and mark @@ -1905,8 +1706,9 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * We also generate (in standard_qp_callback) pathkey representations * of the query's sort clause, distinct clause, etc. */ - current_rel = query_planner(root, tlist, - standard_qp_callback, &qp_extra); + if (current_rel == NULL) + current_rel = query_planner(root, tlist, + standard_qp_callback, &qp_extra); /* * Convert the query's result tlist into PathTarget format. diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 9e001c1bdc..2132c086cc 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -894,7 +894,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->grouping_map = NULL; subroot->minmax_aggs = NIL; subroot->qual_security_level = 0; - subroot->inhTargetKind = INHKIND_NONE; subroot->hasRecursion = false; subroot->wt_param_id = -1; subroot->non_recursive_path = NULL; diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index ca6622ece9..7447853db9 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -18,6 +18,8 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" #include "parser/parsetree.h" #include "utils/lsyscache.h" #include "utils/rel.h" @@ -394,8 +396,164 @@ adjust_appendrel_attrs_mutator(Node *node, context->appinfos); return (Node *) phv; } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + + if (IsA(node, EquivalenceClass)) + { + EquivalenceClass *ec = (EquivalenceClass *) node; + EquivalenceClass *new_ec = makeNode(EquivalenceClass); + AppendRelInfo *appinfo = NULL; + RelOptInfo *parent_rel, + *child_rel; + ListCell *lc; + + /* + * First memcpy the existing EC to the new EC, which creates shallow + * copies of all the members and then make copies of members that + * we'll change below. + * + * XXX comment in _copyPathKey says it's OK to recycle EC + * pointers, but as long as we do the whole planning for a + * given child using a given root, copying ECs like this + * shouldn't be a problem. Maybe, the following code could + * be in _copyEquivalenceClass()? + */ + memcpy(new_ec, ec, sizeof(EquivalenceClass)); + new_ec->ec_opfamilies = list_copy(ec->ec_opfamilies); + new_ec->ec_sources = list_copy(ec->ec_sources); + new_ec->ec_derives = list_copy(ec->ec_derives); + new_ec->ec_relids = bms_copy(ec->ec_relids); + new_ec->ec_members = list_copy(ec->ec_members); + + /* + * No point in searching if parent rel not mentioned in eclass; but we + * can't tell that for sure if parent rel is itself a child. + */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + if (bms_is_member(appinfos[cnt]->parent_relid, ec->ec_relids)) + { + appinfo = appinfos[cnt]; + break; + } + } + + if (appinfo == NULL) + return (Node *) new_ec; + + parent_rel = find_base_rel(context->root, appinfo->parent_relid); + child_rel = find_base_rel(context->root, appinfo->child_relid); + if (parent_rel->reloptkind != RELOPT_BASEREL) + return (Node *) new_ec; + + /* + * Check if this EC contains an expression referencing the parent + * relation, translate it to child, and store it in place of + * the original parent expression. + */ + foreach(lc, new_ec->ec_members) + { + EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc); + + if (cur_em->em_is_const) + continue; /* ignore consts here */ + + /* Does it reference parent_rel? */ + if (bms_overlap(cur_em->em_relids, parent_rel->relids)) + { + /* Yes, generate transformed child version */ + Expr *new_expr; + Relids new_relids; + Relids new_nullable_relids; + + new_expr = (Expr *) + adjust_appendrel_attrs(context->root, + (Node *) cur_em->em_expr, + 1, &appinfo); + + /* + * Transform em_relids to match. Note we do *not* do + * pull_varnos(child_expr) here, as for example the + * transformation might have substituted a constant, but we + * don't want the child member to be marked as constant. + */ + new_relids = bms_difference(cur_em->em_relids, + parent_rel->relids); + new_relids = bms_add_members(new_relids, child_rel->relids); + + /* + * And likewise for nullable_relids. Note this code assumes + * parent and child relids are singletons. + */ + new_nullable_relids = cur_em->em_nullable_relids; + if (bms_overlap(new_nullable_relids, parent_rel->relids)) + { + new_nullable_relids = bms_difference(new_nullable_relids, + parent_rel->relids); + new_nullable_relids = bms_add_members(new_nullable_relids, + child_rel->relids); + } + + /* + * The new expression simply replaces the old parent one, and + * em_is_child is set to true so that it's recognized as such + * during child planning. + */ + lfirst(lc) = add_eq_member(new_ec, new_expr, + new_relids, new_nullable_relids, + false, cur_em->em_datatype); + + /* + * We have found and replaced the parent expression, so done + * with EC. + */ + break; + } + } + + /* + * Now fix up EC's relids set. It's OK to modify EC like this, + * because caller must have made a copy of the original EC. + * For example, see adjust_inherited_target_child_root. + */ + new_ec->ec_relids = bms_del_members(new_ec->ec_relids, + parent_rel->relids); + new_ec->ec_relids = bms_add_members(new_ec->ec_relids, + child_rel->relids); + return (Node *) new_ec; + } + /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index eaf788e578..68ec4aab6a 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -21,11 +21,14 @@ #include "miscadmin.h" #include "optimizer/appendinfo.h" #include "optimizer/inherit.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "utils/rel.h" +static PlannerInfo *create_inherited_target_child_root(PlannerInfo *root, + AppendRelInfo *appinfo); static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti); static void expand_partitioned_rtentry(PlannerInfo *root, @@ -492,3 +495,107 @@ translate_col_privs(const Bitmapset *parent_privs, return child_privs; } + +/* + * add_inherited_target_child_roots + * For each child of the query's result relation, this translates the + * original query to match the child and creates a PlannerInfo containing + * the translated query + * + * Child PlannerInfo reuses most of the parent PlannerInfo's fields unchanged, + * except unexpanded_tlist, processed_tlist, and all_baserels, all of which + * are based on the child relation. + */ +void +add_inherited_target_child_roots(PlannerInfo *root) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; + + Assert(root->inh_target_child_roots != NULL); + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + RangeTblEntry *childRTE; + PlannerInfo *subroot; + + if (appinfo->parent_relid != resultRelation) + continue; + + /* + * Create a PlannerInfo for processing this child target relation + * with. + */ + subroot = create_inherited_target_child_root(root, appinfo); + root->inh_target_child_roots[appinfo->child_relid] = subroot; + + /* + * If the child is a partitioned table, recurse to do this for its + * partitions. + */ + childRTE = root->simple_rte_array[appinfo->child_relid]; + if (childRTE->inh) + add_inherited_target_child_roots(subroot); + } +} + +/* + * create_inherited_target_child_root + * Workhorse of add_inherited_target_child_roots + */ +static PlannerInfo * +create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) +{ + PlannerInfo *subroot; + List *tlist; + + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + + /* + * Translate the original query to replace Vars of the parent table + * by the corresponding Vars of the child table and to make child the main + * target relation of the query. + */ + subroot = makeNode(PlannerInfo); + memcpy(subroot, root, sizeof(PlannerInfo)); + + /* + * Restore the original, unexpanded targetlist, that is, the one before + * preprocess_targetlist was run on the original query. We'll run + * preprocess_targetlist after translating the query and the targetlist, + * so that it is expanded according to child's tuple descriptor. + */ + root->parse->targetList = root->unexpanded_tlist; + subroot->parse = (Query *) adjust_appendrel_attrs(root, + (Node *) root->parse, + 1, &appinfo); + + /* + * Save the just translated targetlist as unexpanded_tlist in the child's + * subroot, so that this child's own children can use it. Must use copy + * because subroot->parse->targetList will be modified soon. + */ + subroot->unexpanded_tlist = list_copy(subroot->parse->targetList); + + /* + * Apply planner's expansion of targetlist, such as adding various junk + * column, filling placeholder entries for dropped columns, etc., all of + * which occurs with the child's TupleDesc. + */ + tlist = preprocess_targetlist(subroot); + subroot->processed_tlist = tlist; + + /* Add any newly added Vars to the child RelOptInfo. */ + build_base_rel_tlists(subroot, tlist); + + /* + * Adjust all_baserels to replace the original target relation with the + * child target relation. Copy it before modifying though. + */ + subroot->all_baserels = adjust_child_relids(subroot->all_baserels, + 1, &appinfo); + + return subroot; +} diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 3efa1bdc1a..f2ee83c81b 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1264,36 +1264,6 @@ get_relation_constraints(PlannerInfo *root, } } - /* - * Append partition predicates, if any. - * - * For selects, partition pruning uses the parent table's partition bound - * descriptor, instead of constraint exclusion which is driven by the - * individual partition's partition constraint. - */ - if (enable_partition_pruning && root->parse->commandType != CMD_SELECT) - { - List *pcqual = RelationGetPartitionQual(relation); - - if (pcqual) - { - /* - * Run the partition quals through const-simplification similar to - * check constraints. We skip canonicalize_qual, though, because - * partition quals should be in canonical form already; also, - * since the qual is in implicit-AND format, we'd have to - * explicitly convert it to explicit-AND format and back again. - */ - pcqual = (List *) eval_const_expressions(root, (Node *) pcqual); - - /* Fix Vars to have the desired varno */ - if (varno != 1) - ChangeVarNodes((Node *) pcqual, 1, varno, 0); - - result = list_concat(result, pcqual); - } - } - table_close(relation, NoLock); return result; @@ -1417,40 +1387,15 @@ relation_excluded_by_constraints(PlannerInfo *root, /* * Skip further tests, depending on constraint_exclusion. */ - switch (constraint_exclusion) - { - case CONSTRAINT_EXCLUSION_OFF: - - /* - * Don't prune if feature turned off -- except if the relation is - * a partition. While partprune.c-style partition pruning is not - * yet in use for all cases (update/delete is not handled), it - * would be a UI horror to use different user-visible controls - * depending on such a volatile implementation detail. Therefore, - * for partitioned tables we use enable_partition_pruning to - * control this behavior. - */ - if (root->inhTargetKind == INHKIND_PARTITIONED) - break; - return false; - - case CONSTRAINT_EXCLUSION_PARTITION: - - /* - * When constraint_exclusion is set to 'partition' we only handle - * OTHER_MEMBER_RELs, or BASERELs in cases where the result target - * is an inheritance parent or a partitioned table. - */ - if ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) && - !(rel->reloptkind == RELOPT_BASEREL && - root->inhTargetKind != INHKIND_NONE && - rel->relid == root->parse->resultRelation)) - return false; - break; - - case CONSTRAINT_EXCLUSION_ON: - break; /* always try to exclude */ - } + if (constraint_exclusion == CONSTRAINT_EXCLUSION_OFF) + return false; + /* + * When constraint_exclusion is set to 'partition' we only handle + * OTHER_MEMBER_RELs. + */ + else if (constraint_exclusion == CONSTRAINT_EXCLUSION_PARTITION && + rel->reloptkind != RELOPT_OTHER_MEMBER_REL) + return false; /* * Check for self-contradictory restriction clauses. We dare not make diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index d3c477a542..b46947ffe9 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -325,9 +325,6 @@ struct PlannerInfo Index qual_security_level; /* minimum security_level for quals */ /* Note: qual_security_level is zero if there are no securityQuals */ - InheritanceKind inhTargetKind; /* indicates if the target relation is an - * inheritance child or partition or a - * partitioned table */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */ bool hasHavingQual; /* true if havingQual was non-null */ @@ -348,9 +345,45 @@ struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* + * The following fields are set during query planning portion of an + * inherited UPDATE/DELETE operation. + */ + bool inherited_update; /* UPDATE/DELETE on inheritance parent? */ + + /* + * This stores the original version of the query's targetlist that's + * not modified by the planner. + */ + List *unexpanded_tlist; + + /* + * Array containing simple_rel_array_size elements, indexed by rangetable + * index (entry 0 is wasted like simple_rel_array). Only elements + * corresponding to individual inheritance child target relations are + * non-NULL. Content of each PlannerInfo is mostly same as the parent + * PlannerInfo, except for few fields such as the parse tree which is + * a translated copy of the parent's parse tree, EC list which contain + * child member expressions, etc. + */ + struct PlannerInfo **inh_target_child_roots; + + /* List of RT indexes of child target relations. */ + List *inh_target_child_rels; + + /* + * RelOptInfos corresponding to each child target rel. For leaf children, + * it's the RelOptInfo representing the output of make_rel_from_joinlist() + * called with the parent rel in the original join tree replaced by a + * given leaf child. For non-leaf children, it's the baserel RelOptInfo + * itself, left as a placeholder. + */ + List *inh_target_child_path_rels; }; + /* * In places where it's known that simple_rte_array[] must have been prepared * already, we just index into it to fetch RTEs. In code that might be diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h index d2418f15cf..4f06cdd322 100644 --- a/src/include/optimizer/inherit.h +++ b/src/include/optimizer/inherit.h @@ -18,5 +18,6 @@ extern void expand_inherited_tables(PlannerInfo *root); +extern void add_inherited_target_child_roots(PlannerInfo *root); #endif /* INHERIT_H */ diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 1b02b3b889..749c1f7f3d 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -132,6 +132,9 @@ typedef bool (*ec_matches_callback_type) (PlannerInfo *root, extern bool process_equivalence(PlannerInfo *root, RestrictInfo **p_restrictinfo, bool below_outer_join); +extern EquivalenceMember *add_eq_member(EquivalenceClass *ec, + Expr *expr, Relids relids, Relids nullable_relids, + bool is_child, Oid datatype); extern Expr *canonicalize_ec_expression(Expr *expr, Oid req_type, Oid req_collation); extern void reconsider_outer_join_clauses(PlannerInfo *root); diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index c55de5d476..c9f02c1fb0 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1780,7 +1780,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_1 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_1 -> Nested Loop Semi Join @@ -1788,7 +1788,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_2 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_2 (28 rows) -- 2.17.2 (Apple Git-113)
^ permalink raw reply [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-02-06 02:03  Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 1 reply; 265+ messages in thread

From: Tsunakawa, Takayuki @ 2019-02-06 02:03 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Imai-san,

From: Amit Langote [mailto:Langote_Amit_f8@lab.ntt.co.jp]
> On 2019/01/22 18:47, David Rowley wrote:
> > On Tue, 22 Jan 2019 at 20:01, Imai, Yoshikazu
> >> What I understand so far is about 10,000 while loops at total
> (4098+4098+some extra) is needed in hash_seq_search() in EXECUTE query
> after the creation of the generic plan.
> >> 10,000 while loops takes about 10 microsec (of course, we can't estimate
> correct time), and the difference of the latency between 5th and 7th EXECUTE
> is about 8 microsec, I currently think this causes the difference.
> >  >
> >> I don't know this problem relates to Amit-san's patch, but I'll continue
> to investigate it.
> >
> > I had another thought... when you're making a custom plan you're only
> > grabbing locks on partitions that were not pruned (just 1 partition in
> > your case), but when making the generic plan, locks will be acquired
> > on all partitions (all 4000 of them). This likely means that when
> > building the generic plan for the first time that the
> > LockMethodLocalHash table is expanded to fit all those locks, and
> > since we never shrink those down again, it'll remain that size for the
> > rest of your run.  I imagine the reason for the slowdown is that
> > during LockReleaseAll(), a sequential scan is performed over the
> > entire hash table. I see from looking at the hash_seq_search() code
> > that the value of max_bucket is pretty critical to how it'll perform.
> > The while ((curElem = segp[segment_ndx]) == NULL) loop will need to
> > run fewer times with a lower max_bucket.
> 
> I too think that that might be behind that slight drop in performance.
> So, it's good to know what one of the performance bottlenecks is when
> dealing with large number of relations in queries.

Can you compare the performance of auto and force_custom_plan again with the attached patch?  It uses PGPROC's LOCALLOCK list instead of the hash table.


Regards
Takayuki Tsunakawa





Attachments:

faster-locallock-scan.patch (application/octet-stream, 6.7K) download diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index 3bb5ce3..9475fe1 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -794,6 +794,7 @@ LockAcquireExtended(const LOCKTAG *locktag, */ if (!found) { + dlist_push_head(&MyProc->localLocks, &locallock->procLink); locallock->lock = NULL; locallock->proclock = NULL; locallock->hashcode = LockTagHashCode(&(localtag.lock)); @@ -1320,6 +1321,7 @@ RemoveLocalLock(LOCALLOCK *locallock) SpinLockRelease(&FastPathStrongRelationLocks->mutex); } + dlist_delete(&locallock->procLink); if (!hash_search(LockMethodLocalHash, (void *) &(locallock->tag), HASH_REMOVE, NULL)) @@ -2088,7 +2090,7 @@ LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock) void LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks) { - HASH_SEQ_STATUS status; + dlist_mutable_iter iter; LockMethod lockMethodTable; int i, numLockModes; @@ -2126,10 +2128,10 @@ LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks) * pointers. Fast-path locks are cleaned up during the locallock table * scan, though. */ - hash_seq_init(&status, LockMethodLocalHash); - - while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL) + dlist_foreach_modify(iter, &MyProc->localLocks) { + locallock = dlist_container(LOCALLOCK, procLink, iter.cur); + /* * If the LOCALLOCK entry is unused, we must've run out of shared * memory while trying to set up this lock. Just forget the local @@ -2362,16 +2364,16 @@ LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks) void LockReleaseSession(LOCKMETHODID lockmethodid) { - HASH_SEQ_STATUS status; + dlist_mutable_iter iter; LOCALLOCK *locallock; if (lockmethodid <= 0 || lockmethodid >= lengthof(LockMethods)) elog(ERROR, "unrecognized lock method: %d", lockmethodid); - hash_seq_init(&status, LockMethodLocalHash); - - while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL) + dlist_foreach_modify(iter, &MyProc->localLocks) { + locallock = dlist_container(LOCALLOCK, procLink, iter.cur); + /* Ignore items that are not of the specified lock method */ if (LOCALLOCK_LOCKMETHOD(*locallock) != lockmethodid) continue; @@ -2394,13 +2396,14 @@ LockReleaseCurrentOwner(LOCALLOCK **locallocks, int nlocks) { if (locallocks == NULL) { - HASH_SEQ_STATUS status; + dlist_mutable_iter iter; LOCALLOCK *locallock; - hash_seq_init(&status, LockMethodLocalHash); - - while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL) + dlist_foreach_modify(iter, &MyProc->localLocks) + { + locallock = dlist_container(LOCALLOCK, procLink, iter.cur); ReleaseLockIfHeld(locallock, false); + } } else { @@ -2493,13 +2496,14 @@ LockReassignCurrentOwner(LOCALLOCK **locallocks, int nlocks) if (locallocks == NULL) { - HASH_SEQ_STATUS status; + dlist_mutable_iter iter; LOCALLOCK *locallock; - hash_seq_init(&status, LockMethodLocalHash); - - while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL) + dlist_foreach_modify(iter, &MyProc->localLocks) + { + locallock = dlist_container(LOCALLOCK, procLink, iter.cur); LockReassignOwner(locallock, parent); + } } else { @@ -3133,8 +3137,7 @@ LockRefindAndRelease(LockMethod lockMethodTable, PGPROC *proc, void AtPrepare_Locks(void) { - HASH_SEQ_STATUS status; - LOCALLOCK *locallock; + dlist_mutable_iter iter; /* * For the most part, we don't need to touch shared memory for this --- @@ -3142,10 +3145,9 @@ AtPrepare_Locks(void) * Fast-path locks are an exception, however: we move any such locks to * the main table before allowing PREPARE TRANSACTION to succeed. */ - hash_seq_init(&status, LockMethodLocalHash); - - while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL) + dlist_foreach_modify(iter, &MyProc->localLocks) { + LOCALLOCK *locallock = dlist_container(LOCALLOCK, procLink, iter.cur); TwoPhaseLockRecord record; LOCALLOCKOWNER *lockOwners = locallock->lockOwners; bool haveSessionLock; @@ -3244,8 +3246,7 @@ void PostPrepare_Locks(TransactionId xid) { PGPROC *newproc = TwoPhaseGetDummyProc(xid); - HASH_SEQ_STATUS status; - LOCALLOCK *locallock; + dlist_mutable_iter iter; LOCK *lock; PROCLOCK *proclock; PROCLOCKTAG proclocktag; @@ -3267,10 +3268,9 @@ PostPrepare_Locks(TransactionId xid) * pointing to the same proclock, and we daren't end up with any dangling * pointers. */ - hash_seq_init(&status, LockMethodLocalHash); - - while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL) + dlist_foreach_modify(iter, &MyProc->localLocks) { + LOCALLOCK *locallock = dlist_container(LOCALLOCK, procLink, iter.cur); LOCALLOCKOWNER *lockOwners = locallock->lockOwners; bool haveSessionLock; bool haveXactLock; diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 89c80fb..a22d73a 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -389,6 +389,7 @@ InitProcess(void) MyProc->lwWaitMode = 0; MyProc->waitLock = NULL; MyProc->waitProcLock = NULL; + dlist_init(&MyProc->localLocks); #ifdef USE_ASSERT_CHECKING { int i; @@ -568,6 +569,7 @@ InitAuxiliaryProcess(void) MyProc->lwWaitMode = 0; MyProc->waitLock = NULL; MyProc->waitProcLock = NULL; + dlist_init(&MyProc->localLocks); #ifdef USE_ASSERT_CHECKING { int i; diff --git a/src/include/storage/lock.h b/src/include/storage/lock.h index 16b927c..20887f4 100644 --- a/src/include/storage/lock.h +++ b/src/include/storage/lock.h @@ -18,6 +18,7 @@ #error "lock.h may not be included from frontend code" #endif +#include "lib/ilist.h" #include "storage/lockdefs.h" #include "storage/backendid.h" #include "storage/lwlock.h" @@ -406,6 +407,7 @@ typedef struct LOCALLOCK /* data */ LOCK *lock; /* associated LOCK object, if any */ PROCLOCK *proclock; /* associated PROCLOCK object, if any */ + dlist_node procLink; /* list link in PGPROC's list of LOCALLOCKs */ uint32 hashcode; /* copy of LOCKTAG's hash value */ int64 nLocks; /* total number of times lock is held */ bool holdsStrongLockCount; /* bumped FastPathStrongRelationLocks */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index d203acb..031e004 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -138,6 +138,7 @@ struct PGPROC /* waitLock and waitProcLock are NULL if not currently waiting. */ LOCK *waitLock; /* Lock object we're sleeping on ... */ PROCLOCK *waitProcLock; /* Per-holder info for awaited lock */ + dlist_head localLocks; /* List of LOCALLOCKs */ LOCKMODE waitLockMode; /* type of lock we're waiting for */ LOCKMASK heldLocks; /* bitmask for lock types already held on this * lock object by this backend */
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-07 10:21  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Amit Langote <amitlangote09@gmail.com>
  0 siblings, 2 replies; 265+ messages in thread

From: Amit Langote @ 2019-02-07 10:21 UTC (permalink / raw)
  To: Amit Langote <amitlangote09@gmail.com>; David Rowley <david.rowley@2ndquadrant.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers

On 2019/02/02 22:52, Amit Langote wrote:
> Attached updated patches.
> 
> One big change is related to how ECs are transferred to child
> PlannerInfos.  As David suggested upthread, I created a block in
> adjust_appendrel_attrs_mutator that creates a translated copy of a
> given EC containing wherein the parent expression in the original
> ec_members list is replaced by the corresponding child expression.
> With that in place, we no longer need the changes to
> add_child_rel_equivalences().  Instead there's just:
> subroot->eq_classes = adjust_appendrel_attrs(root, root->eq_classes,
> ...), just as David described upthread.

Rebased over bdd9a99aac.

That commit fixes the bug that lateral_relids were not propagated to
grandchildren of an appendrel in some cases due to the way parent rels
were mapped to child rels in a nested loop over root->simple_rel_array and
root->append_rel_list.  The problem that was fixed with that commit was
not present with the patches here to begin with.  With the patch 0002
here, lateral_relids are propagated from parent rel to child rel directly
when the latter's RelOptInfo is built, so lateral_relids are properly
propagated from the (possibly RTE_SUBQUERY) top-most parent rel to all the
child rels.

Thanks,
Amit

Attachments: download all (4 files, .tar.gz)

v20-0001-Overhaul-inheritance-update-delete-planning.patch (text/plain, 74.3K) download From 9fe101f375268c7948c45760fa157fc8d9ae7e93 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 17 Oct 2018 11:18:12 +0900 Subject: [PATCH v20 1/4] Overhaul inheritance update/delete planning With the current method, inheritance_planner, which handles the planning for update/delete commands targeting inheritance trees, repeatedly calls query_planner for each child table. That's inefficient for two reasons: 1. partprune.c cannot be used to perform partition pruning, because it can only be invoked from query_planner. With the current approach, query_planner only sees the individual partitions in the query tree it receives, not the parent partitioned table. This leaves each partition to be pruned using constraint exclusion. (constraint exclusion cannon prune hash partitions which means UPDATE/DELETE don't support hash partition pruning.) 2. Repeated invocation of query_planner results in allocating large amounts of memory, especially if there are many child tables. Also, it involves repeatedly performing the same processing, such as jointree processing. This commit addresses both of the issues by modifying query_planner to handle the inheritance target case directly. Instead of having the caller create separate PlannerInfo's for individual child target relations and pass them to query_planner (actually, via grouping_planner), this teaches query_planner itself to create the child PlannerInfo's by the original parent PlannerInfo. It creates them *after* it has generated enough state in the latter, such as RelOptInfos for various relations, EquivalenceClasses after processing the join tree, and *before* generating the scan and join paths. Scan paths are generated normally, except that Paths of target children are not "appended". Join paths are generated separately for each child target relation, that is, by replacing the parent relation references in the original join tree with the child relation references. Joinrels (RelOptInfos thereof) corresponding to various child target relation are saved in the parent PlannerInfo for further processing. inheritance_planner now calls query_planner just once at the beginning with the original unmodified query, which takes care of generating top-level join rels for the child queries as described above. For each child join rel, it then calls grouping_planner using previously created child PlannerInfo to finish up the paths such that they produce query's top-level target list expanded according to a given child relation's descriptor. grouping_planner()'s interface is modified so that we can pass it what's called a 'planned_rel', a RelOptInfo that already contains the necessary paths needed to produce its output. This removes some existing code in inheritance_planner that dealt with any subquery RTEs in the query. The rationale of that code was that the subquery RTEs may change during each iteration of planning (that is, for different children), so different iterations better use different copies of those RTEs. That was handled by making fresh copies of those RTEs for each iteration of planning which were appended to the range table, accompanied by modifying all expressions and auxiliary structures that referenced the original subquery RTEs to instead reference the copies (that is, change the varnos). This copying meant we would end up adding S * N new entries to the original range table by the time we got to the last unpruned child, where S is the number of subquery RTEs in the original query and N the number of unpruned children. Since with the new code we perform planning just once, I think we don't need this special handling. Actually, there is a regression test output change due to no longer having copies of subquery RTEs (see the diff of partition_join.out file.) Also we no longer need the hack in relation_excluded_by_constraints() involving setting PlannerInfo.inhTargetKind to enable constraint exclusion for target child relation, which is no longer needed. Constraint exclusion now runs during query_planner step described above. Regression test output change in partition_join.out is due to the fact that we no longer create duplicates of subquery RTEs in child range tables. --- doc/src/sgml/ddl.sgml | 17 +- src/backend/nodes/outfuncs.c | 2 +- src/backend/optimizer/path/allpaths.c | 511 +++++++++++++++++++++++---- src/backend/optimizer/path/equivclass.c | 5 +- src/backend/optimizer/path/joinrels.c | 10 +- src/backend/optimizer/plan/createplan.c | 10 - src/backend/optimizer/plan/planmain.c | 50 ++- src/backend/optimizer/plan/planner.c | 436 ++++++----------------- src/backend/optimizer/prep/prepjointree.c | 1 - src/backend/optimizer/util/appendinfo.c | 160 ++++++++- src/backend/optimizer/util/inherit.c | 107 ++++++ src/backend/optimizer/util/plancat.c | 73 +--- src/include/nodes/pathnodes.h | 39 +- src/include/optimizer/inherit.h | 1 + src/include/optimizer/paths.h | 3 + src/test/regress/expected/partition_join.out | 4 +- 16 files changed, 937 insertions(+), 492 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index ef713a5a1c..f4331bcf65 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -4439,24 +4439,11 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <note> <para> - Currently, pruning of partitions during the planning of an - <command>UPDATE</command> or <command>DELETE</command> command is - implemented using the constraint exclusion method (however, it is - controlled by the <literal>enable_partition_pruning</literal> rather than - <literal>constraint_exclusion</literal>) &mdash; see the following section - for details and caveats that apply. - </para> - - <para> Execution-time partition pruning currently only occurs for the <literal>Append</literal> and <literal>MergeAppend</literal> node types. It is not yet implemented for the <literal>ModifyTable</literal> node - type. - </para> - - <para> - Both of these behaviors are likely to be changed in a future release - of <productname>PostgreSQL</productname>. + type, but that is likely to be changed in a future release of + <productname>PostgreSQL</productname>. </para> </note> </sect2> diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index f97cf37f1f..2527bc552e 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2210,7 +2210,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); WRITE_UINT_FIELD(qual_security_level); - WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind); WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasHavingQual); @@ -2220,6 +2219,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_BITMAPSET_FIELD(curOuterRels); WRITE_NODE_FIELD(curOuterParams); WRITE_BOOL_FIELD(partColsUpdated); + WRITE_BOOL_FIELD(inherited_update); } static void diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 55b871c02c..f07db81a80 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -39,6 +39,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" @@ -92,8 +93,12 @@ static void set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); +static void set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte); static void set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); +static void set_inherit_target_rel_pathlists(PlannerInfo *root, + RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, @@ -122,6 +127,8 @@ static void set_result_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); +static RelOptInfo *inheritance_make_rel_from_joinlist(PlannerInfo *root, + List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, pushdown_safety_info *safetyInfo); static bool recurse_pushdown_safe(Node *setOp, Query *topquery, @@ -155,27 +162,6 @@ make_one_rel(PlannerInfo *root, List *joinlist) Index rti; double total_pages; - /* - * Construct the all_baserels Relids set. - */ - root->all_baserels = NULL; - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - - /* there may be empty slots corresponding to non-baserel RTEs */ - if (brel == NULL) - continue; - - Assert(brel->relid == rti); /* sanity check on array */ - - /* ignore RTEs that are "other rels" */ - if (brel->reloptkind != RELOPT_BASEREL) - continue; - - root->all_baserels = bms_add_member(root->all_baserels, brel->relid); - } - /* Mark base rels as to whether we care about fast-start plans */ set_base_rel_consider_startup(root); @@ -223,13 +209,34 @@ make_one_rel(PlannerInfo *root, List *joinlist) /* * Generate access paths for the entire join tree. + * + * For UPDATE/DELETE on an inheritance parent, join paths should be + * generated for each child result rel separately. */ - rel = make_rel_from_joinlist(root, joinlist); + if (root->inherited_update) + { + /* + * RelOptInfo corresponding to the query's original target relation + * is returned. Join paths (if any) are attached to child joinrels, + * not this rel. Also, this rel's sole path (ModifyTable) will be set + * by inheritance_planner later, so we can't check its paths yet. + */ + rel = inheritance_make_rel_from_joinlist(root, joinlist); + } + else + { + rel = make_rel_from_joinlist(root, joinlist); - /* - * The result should join all and only the query's base rels. - */ - Assert(bms_equal(rel->relids, root->all_baserels)); + /* + * The result should join all and only the query's base rels. + */ + Assert(bms_equal(rel->relids, root->all_baserels)); + + /* Check that we got at least one usable path */ + if (!rel || !rel->cheapest_total_path || + rel->cheapest_total_path->param_info != NULL) + elog(ERROR, "failed to construct the join relation"); + } return rel; } @@ -313,9 +320,10 @@ set_base_rel_sizes(PlannerInfo *root) * If parallelism is allowable for this query in general, see whether * it's allowable for this rel in particular. We have to do this * before set_rel_size(), because (a) if this rel is an inheritance - * parent, set_append_rel_size() will use and perhaps change the rel's - * consider_parallel flag, and (b) for some RTE types, set_rel_size() - * goes ahead and makes paths immediately. + * parent, set_append_rel_size() or set_inherit_target_rel_sizes() + * will use and perhaps change the rel's consider_parallel flag, and + * (b) for some RTE types, set_rel_size() goes ahead and makes paths + * immediately. */ if (root->glob->parallelModeOK) set_rel_consider_parallel(root, rel, rte); @@ -379,8 +387,15 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_size(root, rel, rti, rte); + /* + * If it's a target relation, set the sizes of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (rti == root->parse->resultRelation) + set_inherit_target_rel_sizes(root, rel, rti, rte); + else + set_append_rel_size(root, rel, rti, rte); } else { @@ -469,14 +484,26 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { + bool inherited_update = false; + if (IS_DUMMY_REL(rel)) { /* We already proved the relation empty, so nothing more to do */ } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_pathlist(root, rel, rti, rte); + /* + * If it's a target relation, set the pathlists of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (root->inherited_update && root->parse->resultRelation == rti) + { + inherited_update = true; + set_inherit_target_rel_pathlists(root, rel, rti, rte); + } + else + set_append_rel_pathlist(root, rel, rti, rte); } else { @@ -554,8 +581,12 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, if (set_rel_pathlist_hook) (*set_rel_pathlist_hook) (root, rel, rti, rte); - /* Now find the cheapest of the paths for this rel */ - set_cheapest(rel); + /* + * Now find the cheapest of the paths for this rel, unless it's an + * inheritance parent and this is an update/delete operation. + */ + if (!inherited_update) + set_cheapest(rel); #ifdef OPTIMIZER_DEBUG debug_print_rel(root, rel); @@ -921,6 +952,225 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) } /* + * set_inherit_target_rel_sizes + * Set size estimates for the child target relations + * + * The passed-in rel represents the target relation of the query that is + * known to have inheritance children. This is very much like + * set_append_rel_size, except it doesn't set the size estimates for the + * passed-in rel itself, because we don't need to "append" the children + * in this case. + */ +static void +set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + bool has_live_children = false; + ListCell *l; + Relids live_children = NULL; + bool did_pruning = false; + + /* Guard against stack overflow due to overly deep inheritance tree. */ + check_stack_depth(); + + Assert(IS_SIMPLE_REL(rel)); + + /* + * If the partitioned relation has any baserestrictinfo quals then we + * attempt to use these quals to prune away partitions that cannot + * possibly contain any tuples matching these quals. In this case we'll + * store the relids of all partitions which could possibly contain a + * matching tuple, and skip anything else in the loop below. + */ + if (enable_partition_pruning && + rte->relkind == RELKIND_PARTITIONED_TABLE && + rel->baserestrictinfo != NIL) + { + live_children = prune_append_rel_partitions(rel); + did_pruning = true; + } + + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + PlannerInfo *subroot; + ListCell *lc; + List *translated_exprs, + *child_target_exprs; + bool childpruned; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + + /* + * The child rel's RelOptInfo was already created during + * add_base_rels_to_query. + */ + childrel = find_base_rel(root, childRTindex); + Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); + + childpruned = (did_pruning && + !bms_is_member(appinfo->child_relid, live_children)); + + /* + * Unless the child is pruned, 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 baserestrictinfo quals. + */ + if (childpruned || + !apply_child_basequals(root, rel, childrel, childRTE, appinfo) || + relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* This partition needn't be scanned; skip it. */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Add missing Vars to child's reltarget. + * + * add_inherit_target_child_root() would've added only those that are + * needed to be present in the top-level tlist (or ones that + * preprocess_targetlist thinks are needed to be in the tlist.) We + * may need other attributes such as those contained in WHERE clauses, + * which are already computed for the parent during + * deconstruct_jointree processing of the original query (child's + * query never goes through deconstruct_jointree.) + */ + translated_exprs = (List *) + adjust_appendrel_attrs(root, + (Node *) rel->reltarget->exprs, + 1, &appinfo); + child_target_exprs = childrel->reltarget->exprs; + foreach(lc, translated_exprs) + { + Expr *expr = lfirst(lc); + + if (!list_member(child_target_exprs, expr)) + child_target_exprs = lappend(child_target_exprs, expr); + } + + subroot = root->inh_target_child_roots[childRTindex]; + + /* + * Also, We have to find any ECs containing parent's expressions and + * *replace* them with their copies containing child expressions. + */ + if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) + subroot->eq_classes = (List *) + adjust_appendrel_attrs(root, + (Node *) root->eq_classes, + 1, &appinfo); + childrel->has_eclass_joins = rel->has_eclass_joins; + + /* Translate join quals. */ + childrel->joininfo = (List *) + adjust_appendrel_attrs(subroot, + (Node *) rel->joininfo, + 1, &appinfo); + + /* + * Compute the child's size using possibly modified subroot. + */ + set_rel_size(subroot, childrel, childRTindex, childRTE); + + /* If the child itself is partitioned it may turn into a dummy rel. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* We have at least one live child. */ + has_live_children = true; + + Assert(childrel->rows > 0); + } + + if (has_live_children) + { + /* + * Set a non-zero value here to cope with the caller's requirement + * that non-dummy relations are actually not empty. We don't try to + * be accurate here, because we're not going to create a path that + * combines the children outputs. + */ + rel->rows = 1; + } + else + { + /* + * All children were excluded by constraints, so mark the relation + * as dummy. We must do this in this phase so that the rel's + * dummy-ness is visible when we generate paths for other rels. + */ + set_dummy_rel_pathlist(rel); + } +} + +/* + * set_inherit_target_rel_pathlists + * Build access paths for the child target relations + * + * Similar to set_append_rel_pathlist, except that we build paths of the + * children, but don't build an Append path. + */ +static void +set_inherit_target_rel_pathlists(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + ListCell *l; + + /* Nothing to do if all the children were excluded. */ + if (IS_DUMMY_REL(rel)) + return; + + /* Generate access paths for each of the children of passed-in rel */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + PlannerInfo *subroot; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + /* Re-locate the child RTE and RelOptInfo */ + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + childrel = root->simple_rel_array[childRTindex]; + subroot = root->inh_target_child_roots[childRTindex]; + /* Transfer the value from main root to subroot. */ + subroot->total_table_pages = root->total_table_pages; + + /* + * If set_append_rel_size() decided the parent appendrel was + * parallel-unsafe at some point after visiting this child rel, we + * need to propagate the unsafety marking down to the child, so that + * we don't generate useless partial paths for it. + */ + if (!rel->consider_parallel) + childrel->consider_parallel = false; + + /* + * Compute the child's access paths. + */ + set_rel_pathlist(subroot, childrel, childRTindex, childRTE); + } +} + +/* * set_append_rel_size * Set size estimates for a simple "append relation" * @@ -1015,6 +1265,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, RelOptInfo *childrel; ListCell *parentvars; ListCell *childvars; + bool childpruned; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1030,36 +1281,20 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - + childpruned = (did_pruning && + !bms_is_member(appinfo->child_relid, live_children)); /* - * 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 - * baserestrictinfo quals. + * Unless the child is pruned, 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 baserestrictinfo quals. */ - if (!apply_child_basequals(root, rel, childrel, childRTE, appinfo)) + if (childpruned || + !apply_child_basequals(root, rel, childrel, childRTE, appinfo) || + relation_excluded_by_constraints(root, childrel, childRTE)) { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* - * This child need not be scanned, so we can omit it from the - * appendrel. - */ + /* This partition needn't be scanned; skip it. */ set_dummy_rel_pathlist(childrel); continue; } @@ -2563,6 +2798,158 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows) } /* + * inheritance_make_rel_from_joinlist + * Perform join planning for all non-dummy leaf inheritance children + * in their role as an UPDATE/DELETE query's target relation + * + * If a child relation is a partitioned table, its children are processed in + * turn by recursively calling this function. + */ +static RelOptInfo * +inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) +{ + Index resultRelation = root->parse->resultRelation; + RelOptInfo *resultrel; + ListCell *lc; +#ifdef USE_ASSERT_CHECKING + Relids all_baserels; +#endif + + /* For UPDATE/DELETE queries, the top parent can only ever be a table. */ + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + Assert(planner_rt_fetch(resultRelation, root)->rtekind == RTE_RELATION); + resultrel = find_base_rel(root, resultRelation); + + /* Nothing to do. */ + if (IS_DUMMY_REL(resultrel)) + return resultrel; + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + PlannerInfo *subroot; + RelOptInfo *childrel; + RelOptInfo *childjoinrel; + List *translated_joinlist; + + if (appinfo->parent_relid != resultRelation) + continue; + + childrel = find_base_rel(root, appinfo->child_relid); + + /* Ignore excluded/pruned children. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* Add this child. */ + root->inh_target_child_rels = lappend_int(root->inh_target_child_rels, + appinfo->child_relid); + + /* Perform join planning with child subroot. */ + subroot = root->inh_target_child_roots[appinfo->child_relid]; + Assert(subroot->parse->resultRelation > 0); + + /* + * Modify joinlist such that relations joined to the top parent rel + * appear to be joined to the child rel instead. Do the same for + * any SpecialJoinInfo structs. + */ + translated_joinlist = (List *) + adjust_appendrel_attrs(subroot, + (Node *) joinlist, + 1, &appinfo); + subroot->join_info_list = (List *) + adjust_appendrel_attrs(subroot, + (Node *) root->join_info_list, + 1, &appinfo); + + /* + * Sub-partitioned tables have to be processed recursively using the + * translated subroot as the parent, because AppendRelInfos link + * sub-partitions to their immediate parents, not the root partitioned + * table. + */ + if (childrel->part_scheme != NULL) + { + /* + * inheritance_make_rel_from_joinlist() return the target relation + * RelOptInfo. + */ + childrel = + inheritance_make_rel_from_joinlist(subroot, + translated_joinlist); + + /* + * Add this child relation as a placeholder in the parent root's + * inh_target_child_path_rels so that inheritance_planner sees + * same number of entries in it as inh_target_child_rels. + */ + root->inh_target_child_path_rels = + lappend(root->inh_target_child_path_rels, childrel); + + /* + * Also propagate this child's own children into the parent's + * list. + */ + if (subroot->inh_target_child_rels != NIL) + { + root->inh_target_child_rels = + list_concat(root->inh_target_child_rels, + subroot->inh_target_child_rels); + root->inh_target_child_path_rels = + list_concat(root->inh_target_child_path_rels, + subroot->inh_target_child_path_rels); + } + continue; + } + + /* + * Since we added the child rel directly into the join tree, we must + * modify it to be a "base" rel instead of an "other" rel, which the + * join planning code expects the relations being joined to be. + */ + childrel->reloptkind = RELOPT_BASEREL; + + Assert(subroot->join_rel_list == NIL); + Assert(subroot->join_rel_hash == NULL); + + /* Perform join planning and save the resulting RelOptInfo. */ + childjoinrel = make_rel_from_joinlist(subroot, translated_joinlist); + + /* Check that we got at least one usable path */ + if (!childjoinrel || !childjoinrel->cheapest_total_path || + childjoinrel->cheapest_total_path->param_info != NULL) + elog(ERROR, "failed to construct the child join relation"); + + /* + * Remember the paths of child target rel. inheritance_planner will + * perform the remaining steps of planning for each child relation + * separately. Specifically, it will call grouping_planner on every + * RelOptInfo contained in the inh_target_child_rels list, each of + * which represents the source of tuples to be modified for a given + * target child rel. + */ + root->inh_target_child_path_rels = + lappend(root->inh_target_child_path_rels, childjoinrel); +#ifdef USE_ASSERT_CHECKING + /* + * The following implements essentially the same Assert as in + * make_one_rel, our caller. + */ + all_baserels = bms_copy(root->all_baserels); + all_baserels = bms_del_member(all_baserels, + root->parse->resultRelation); + all_baserels = bms_add_member(all_baserels, + subroot->parse->resultRelation); + Assert(bms_equal(childjoinrel->relids, all_baserels)); +#endif + } + + return resultrel; +} + +/* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. * diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 3454f12912..332dc34276 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -32,9 +32,6 @@ #include "utils/lsyscache.h" -static EquivalenceMember *add_eq_member(EquivalenceClass *ec, - Expr *expr, Relids relids, Relids nullable_relids, - bool is_child, Oid datatype); static void generate_base_implied_equalities_const(PlannerInfo *root, EquivalenceClass *ec); static void generate_base_implied_equalities_no_const(PlannerInfo *root, @@ -540,7 +537,7 @@ canonicalize_ec_expression(Expr *expr, Oid req_type, Oid req_collation) /* * add_eq_member - build a new EquivalenceMember and add it to an EC */ -static EquivalenceMember * +EquivalenceMember * add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids, Relids nullable_relids, bool is_child, Oid datatype) { diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index dfbbfdac6d..a02321486f 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1462,8 +1462,14 @@ update_child_rel_info(PlannerInfo *root, (Node *) rel->reltarget->exprs, 1, &appinfo); - /* Make child entries in the EquivalenceClass as well */ - if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) + /* + * Make child entries in the EquivalenceClass as well. If the childrel + * appears to be a dummy one (one built by build_dummy_partition_rel()), + * no need to make any new entries, because anything that'd need those + * can instead use the parent's (rel). + */ + if (childrel->relid != rel->relid && + (rel->has_eclass_joins || has_useful_pathkeys(root, rel))) add_child_rel_equivalences(root, appinfo, rel, childrel); childrel->has_eclass_joins = rel->has_eclass_joins; } diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 1b4f7db649..496c560cad 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -2049,12 +2049,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) /* * During setrefs.c, we'll need the grouping_map to fix up the cols lists * in GroupingFunc nodes. Save it for setrefs.c to use. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be grouping in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->grouping_map == NULL); root->grouping_map = grouping_map; @@ -2211,12 +2206,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) * with InitPlan output params. (We can't just do that locally in the * MinMaxAgg node, because path nodes above here may have Agg references * as well.) Save the mmaggregates list to tell setrefs.c to do that. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be aggregates in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->minmax_aggs == NIL); root->minmax_aggs = best_path->mmaggregates; diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 3cedd01c98..1cec292907 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -20,6 +20,7 @@ */ #include "postgres.h" +#include "nodes/pathnodes.h" #include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/inherit.h" @@ -29,7 +30,7 @@ #include "optimizer/paths.h" #include "optimizer/placeholder.h" #include "optimizer/planmain.h" - +#include "optimizer/prep.h" /* * query_planner @@ -60,6 +61,7 @@ query_planner(PlannerInfo *root, List *tlist, Query *parse = root->parse; List *joinlist; RelOptInfo *final_rel; + Index rti; /* * Init planner lists to empty. @@ -260,14 +262,50 @@ query_planner(PlannerInfo *root, List *tlist, extract_restriction_or_clauses(root); /* + * Construct the all_baserels Relids set. + */ + root->all_baserels = NULL; + for (rti = 1; rti < root->simple_rel_array_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + Assert(brel->relid == rti); /* sanity check on array */ + + /* ignore RTEs that are "other rels" */ + if (brel->reloptkind != RELOPT_BASEREL) + continue; + + root->all_baserels = bms_add_member(root->all_baserels, brel->relid); + } + + /* + * The "actual" inheritance situation of the relations must be final at + * this point. Good time to set whether the query is an inherited + * UPDATE/DELETE. + */ + root->inherited_update = (parse->resultRelation && + root->simple_rte_array[parse->resultRelation]->inh); + + /* + * Add child subroots needed to use during planning for individual child + * targets + */ + if (root->inherited_update) + { + root->inh_target_child_roots = (PlannerInfo **) + palloc0(root->simple_rel_array_size * + sizeof(PlannerInfo *)); + add_inherited_target_child_roots(root); + } + + /* * Ready to do the primary planning. */ final_rel = make_one_rel(root, joinlist); - /* Check that we got at least one usable path */ - if (!final_rel || !final_rel->cheapest_total_path || - final_rel->cheapest_total_path->param_info != NULL) - elog(ERROR, "failed to construct the join relation"); - return final_rel; } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index b2239728cf..0e97ac704a 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -131,7 +131,7 @@ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static void inheritance_planner(PlannerInfo *root); static void grouping_planner(PlannerInfo *root, bool inheritance_update, - double tuple_fraction); + RelOptInfo *planned_rel, double tuple_fraction); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, int *tleref_to_colnum_map); @@ -636,7 +636,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->grouping_map = NULL; root->minmax_aggs = NIL; root->qual_security_level = 0; - root->inhTargetKind = INHKIND_NONE; root->hasRecursion = hasRecursion; if (hasRecursion) root->wt_param_id = assign_special_exec_param(root); @@ -997,7 +996,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh) inheritance_planner(root); else - grouping_planner(root, false, tuple_fraction); + grouping_planner(root, false, NULL, tuple_fraction); /* * Capture the set of outer-level param IDs we have access to, for use in @@ -1173,13 +1172,41 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr) * inheritance set. * * We have to handle this case differently from cases where a source relation - * is an inheritance set. Source inheritance is expanded at the bottom of the - * plan tree (see allpaths.c), but target inheritance has to be expanded at - * the top. The reason is that for UPDATE, each target relation needs a - * different targetlist matching its own column set. Fortunately, + * is an inheritance set, where outputs of all the children are combined and + * combined output consumed using the source relation's (parent's) column set. + * For UPDATE, each target relation, where the query's output will go, needs + * a different targetlist matching its own column set. So, we will need to + * translate the query such that it produces the desired output required by + * each of the child target relations and plan each separately. Fortunately, * the UPDATE/DELETE target can never be the nullable side of an outer join, * so it's OK to generate the plan this way. * + * Planning steps that need to be performed separately for each child and + * any preliminary processing needed for that are handled by the following + * sub-routines of query_planner: + * + * add_inherit_target_roots - this creates copies of PlannerInfo for each + * child after query_planner has finished processing the join tree and + * creating EquivalenceClasses, so that working state of planning need + * not be created afresh for each child, especially the various arrays + * + * set_inherit_target_rel_sizes - this sets size estimates for child + * relations, replace the parent EC members by corresponding child ones + * in their respective subroots + * + * set_inherit_target_rel_pathlists - this creates Paths for scanning + * individual child relations + * + * inherit_make_rel_from_joinlist - this translates the jointree, replacing + * the target relation in the original jointree (the root parent) by + * individual child target relations and performs join planning on the + * resulting join tree, saving the RelOptInfos of resulting join relations + * into the top-level PlannerInfo + * + * Finally, here we apply grouping_planner to each child join relation + * produced by set_inherit_target_rel_pathlists to so that its path produces + * the desired targetlist. + * * Returns nothing; the useful output is in the Paths we attach to * the (UPPERREL_FINAL, NULL) upperrel stored in *root. * @@ -1191,14 +1218,8 @@ inheritance_planner(PlannerInfo *root) { Query *parse = root->parse; int top_parentRTindex = parse->resultRelation; - Bitmapset *subqueryRTindexes; - Bitmapset *modifiableARIindexes; int nominalRelation = -1; Index rootRelation = 0; - List *final_rtable = NIL; - int save_rel_array_size = 0; - RelOptInfo **save_rel_array = NULL; - AppendRelInfo **save_append_rel_array = NULL; List *subpaths = NIL; List *subroots = NIL; List *resultRelations = NIL; @@ -1206,70 +1227,49 @@ inheritance_planner(PlannerInfo *root) List *returningLists = NIL; List *rowMarks; RelOptInfo *final_rel; - ListCell *lc; - Index rti; + ListCell *lc1, + *lc2; RangeTblEntry *parent_rte; - PlannerInfo *parent_root; - Query *parent_parse; - Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); - PlannerInfo **parent_roots = NULL; + List *tlist; + standard_qp_extra qp_extra; + RelOptInfo *planned_rel; + /* Inheritance is never used for insert. */ Assert(parse->commandType != CMD_INSERT); /* - * We generate a modified instance of the original Query for each target - * relation, plan that, and put all the plans into a list that will be - * controlled by a single ModifyTable node. All the instances share the - * same rangetable, but each instance must have its own set of subquery - * RTEs within the finished rangetable because (1) they are likely to get - * scribbled on during planning, and (2) it's not inconceivable that - * subqueries could get planned differently in different cases. We need - * not create duplicate copies of other RTE kinds, in particular not the - * target relations, because they don't have either of those issues. Not - * having to duplicate the target relations is important because doing so - * (1) would result in a rangetable of length O(N^2) for N targets, with - * at least O(N^3) work expended here; and (2) would greatly complicate - * management of the rowMarks list. + * Let query_planner generate the access paths for the query for each + * target child relation. * - * To begin with, generate a bitmapset of the relids of the subquery RTEs. + * First, save the unexpanded version of the query's targetlist. + * create_inherit_target_child_root will use it as base when expanding + * it for a given child relation as the query's target relation instead + * of the parent. */ - subqueryRTindexes = NULL; - rti = 1; - foreach(lc, parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); + root->unexpanded_tlist = list_copy(root->parse->targetList); - if (rte->rtekind == RTE_SUBQUERY) - subqueryRTindexes = bms_add_member(subqueryRTindexes, rti); - rti++; - } + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root); + root->processed_tlist = tlist; + qp_extra.tlist = tlist; + qp_extra.activeWindows = NIL; + qp_extra.groupClause = NIL; + planned_rel = query_planner(root, tlist, standard_qp_callback, &qp_extra); /* - * Next, we want to identify which AppendRelInfo items contain references - * to any of the aforesaid subquery RTEs. These items will need to be - * copied and modified to adjust their subquery references; whereas the - * other ones need not be touched. It's worth being tense over this - * because we can usually avoid processing most of the AppendRelInfo - * items, thereby saving O(N^2) space and time when the target is a large - * inheritance tree. We can identify AppendRelInfo items by their - * child_relid, since that should be unique within the list. + * If it turned out during query planning that all the children are dummy + * (pruned or excluded by constraints), no need to do the steps below. + * Let grouping_planner finish up the final path. */ - modifiableARIindexes = NULL; - if (subqueryRTindexes != NULL) + if (IS_DUMMY_REL(planned_rel)) { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - - if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) || - bms_is_member(appinfo->child_relid, subqueryRTindexes) || - bms_overlap(pull_varnos((Node *) appinfo->translated_vars), - subqueryRTindexes)) - modifiableARIindexes = bms_add_member(modifiableARIindexes, - appinfo->child_relid); - } + grouping_planner(root, false, planned_rel, 0.0); + return; } + Assert(planned_rel->relid == top_parentRTindex); + Assert(planned_rel->reloptkind == RELOPT_BASEREL); + /* * If the parent RTE is a partitioned table, we should use that as the * nominal target relation, because the RTEs added for partitioned tables @@ -1277,7 +1277,7 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); + parent_rte = planner_rt_fetch(top_parentRTindex, root); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1285,95 +1285,39 @@ inheritance_planner(PlannerInfo *root) } /* - * The PlannerInfo for each child is obtained by translating the relevant - * members of the PlannerInfo for its immediate parent, which we find - * using the parent_relid in its AppendRelInfo. We save the PlannerInfo - * for each parent in an array indexed by relid for fast retrieval. Since - * the maximum number of parents is limited by the number of RTEs in the - * query, we use that number to allocate the array. An extra entry is - * needed since relids start from 1. + * Get on with finalizing the path for each child target relation by + * calling grouping_planner on its joinrel. */ - parent_roots = (PlannerInfo **) palloc0((list_length(parse->rtable) + 1) * - sizeof(PlannerInfo *)); - parent_roots[top_parentRTindex] = root; - - /* - * And now we can get on with generating a plan for each child table. - */ - foreach(lc, root->append_rel_list) + forboth(lc1, root->inh_target_child_rels, + lc2, root->inh_target_child_path_rels) { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - PlannerInfo *subroot; + Index childRTindex = lfirst_int(lc1); + RelOptInfo *childjoinrel = lfirst(lc2); + PlannerInfo *subroot = root->inh_target_child_roots[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; RangeTblEntry *child_rte; RelOptInfo *sub_final_rel; Path *subpath; - /* append_rel_list contains all append rels; ignore others */ - if (!bms_is_member(appinfo->parent_relid, parent_relids)) - continue; - - /* - * expand_inherited_rtentry() always processes a parent before any of - * that parent's children, so the parent_root for this relation should - * already be available. - */ - parent_root = parent_roots[appinfo->parent_relid]; - Assert(parent_root != NULL); - parent_parse = parent_root->parse; - - /* - * We need a working copy of the PlannerInfo so that we can control - * propagation of information back to the main copy. - */ - subroot = makeNode(PlannerInfo); - memcpy(subroot, parent_root, sizeof(PlannerInfo)); - - /* - * Generate modified query with this rel as target. We first apply - * adjust_appendrel_attrs, which copies the Query and changes - * references to the parent RTE to refer to the current child RTE, - * then fool around with subquery RTEs. - */ - subroot->parse = (Query *) - adjust_appendrel_attrs(parent_root, - (Node *) parent_parse, - 1, &appinfo); + Assert(subroot != NULL); + Assert(subroot->parse->resultRelation == childRTindex); /* * If there are securityQuals attached to the parent, move them to the * child rel (they've already been transformed properly for that). */ - parent_rte = rt_fetch(appinfo->parent_relid, subroot->parse->rtable); - child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable); + parent_rte = planner_rt_fetch(appinfo->parent_relid, subroot); + child_rte = planner_rt_fetch(appinfo->child_relid, subroot); child_rte->securityQuals = parent_rte->securityQuals; parent_rte->securityQuals = NIL; /* - * Mark whether we're planning a query to a partitioned table or an - * inheritance parent. + * Ignore a partitioned child. Instead, the paths of its children will + * be added to subpaths. */ - subroot->inhTargetKind = - (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED; - - /* - * If this child is further partitioned, remember it as a parent. - * Since a partitioned table does not have any data, we don't need to - * create a plan for it, and we can stop processing it here. We do, - * however, need to remember its modified PlannerInfo for use when - * processing its children, since we'll update their varnos based on - * the delta from immediate parent to child, not from top to child. - * - * Note: a very non-obvious point is that we have not yet added - * duplicate subquery RTEs to the subroot's rtable. We mustn't, - * because then its children would have two sets of duplicates, - * confusing matters. - */ - if (child_rte->inh) + if (childjoinrel->part_scheme) { - Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE); - parent_relids = bms_add_member(parent_relids, appinfo->child_relid); - parent_roots[appinfo->child_relid] = subroot; - + Assert(child_rte->inh); continue; } @@ -1401,111 +1345,8 @@ inheritance_planner(PlannerInfo *root) if (nominalRelation < 0) nominalRelation = appinfo->child_relid; - /* - * The rowMarks list might contain references to subquery RTEs, so - * make a copy that we can apply ChangeVarNodes to. (Fortunately, the - * executor doesn't need to see the modified copies --- we can just - * pass it the original rowMarks list.) - */ - subroot->rowMarks = copyObject(parent_root->rowMarks); - - /* - * The append_rel_list likewise might contain references to subquery - * RTEs (if any subqueries were flattenable UNION ALLs). So prepare - * to apply ChangeVarNodes to that, too. As explained above, we only - * want to copy items that actually contain such references; the rest - * can just get linked into the subroot's append_rel_list. - * - * If we know there are no such references, we can just use the outer - * append_rel_list unmodified. - */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - subroot->append_rel_list = NIL; - foreach(lc2, parent_root->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, modifiableARIindexes)) - appinfo2 = copyObject(appinfo2); - - subroot->append_rel_list = lappend(subroot->append_rel_list, - appinfo2); - } - } - - /* - * Add placeholders to the child Query's rangetable list to fill the - * RT indexes already reserved for subqueries in previous children. - * These won't be referenced, so there's no need to make them very - * valid-looking. - */ - while (list_length(subroot->parse->rtable) < list_length(final_rtable)) - subroot->parse->rtable = lappend(subroot->parse->rtable, - makeNode(RangeTblEntry)); - - /* - * If this isn't the first child Query, generate duplicates of all - * subquery RTEs, and adjust Var numbering to reference the - * duplicates. To simplify the loop logic, we scan the original rtable - * not the copy just made by adjust_appendrel_attrs; that should be OK - * since subquery RTEs couldn't contain any references to the target - * rel. - */ - if (final_rtable != NIL && subqueryRTindexes != NULL) - { - ListCell *lr; - - rti = 1; - foreach(lr, parent_parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lr); - - if (bms_is_member(rti, subqueryRTindexes)) - { - Index newrti; - - /* - * The RTE can't contain any references to its own RT - * index, except in its securityQuals, so we can save a - * few cycles by applying ChangeVarNodes to the rest of - * the rangetable before we append the RTE to it. - */ - newrti = list_length(subroot->parse->rtable) + 1; - ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); - ChangeVarNodes((Node *) subroot->rowMarks, rti, newrti, 0); - /* Skip processing unchanging parts of append_rel_list */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - foreach(lc2, subroot->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, - modifiableARIindexes)) - ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); - } - } - rte = copyObject(rte); - ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); - subroot->parse->rtable = lappend(subroot->parse->rtable, - rte); - } - rti++; - } - } - - /* There shouldn't be any OJ info to translate, as yet */ - Assert(subroot->join_info_list == NIL); - /* and we haven't created PlaceHolderInfos, either */ - Assert(subroot->placeholder_list == NIL); - - /* Generate Path(s) for accessing this result relation */ - grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ ); + /* Apply the top-level targetlist to childjoinrel's path. */ + grouping_planner(subroot, true, childjoinrel, 0.0); /* * Select cheapest path in case there's more than one. We always run @@ -1517,45 +1358,10 @@ inheritance_planner(PlannerInfo *root) subpath = sub_final_rel->cheapest_total_path; /* - * If this child rel was excluded by constraint exclusion, exclude it - * from the result plan. + * child rel cannot be empty, or inheritance_make_rel_from_joinlist + * wouldn't have put it in the list to begin with. */ - if (IS_DUMMY_PATH(subpath)) - continue; - - /* - * If this is the first non-excluded child, its post-planning rtable - * becomes the initial contents of final_rtable; otherwise, append - * just its modified subquery RTEs to final_rtable. - */ - if (final_rtable == NIL) - final_rtable = subroot->parse->rtable; - else - final_rtable = list_concat(final_rtable, - list_copy_tail(subroot->parse->rtable, - list_length(final_rtable))); - - /* - * We need to collect all the RelOptInfos from all child plans into - * the main PlannerInfo, since setrefs.c will need them. We use the - * last child's simple_rel_array (previous ones are too short), so we - * have to propagate forward the RelOptInfos that were already built - * in previous children. - */ - Assert(subroot->simple_rel_array_size >= save_rel_array_size); - for (rti = 1; rti < save_rel_array_size; rti++) - { - RelOptInfo *brel = save_rel_array[rti]; - - if (brel) - subroot->simple_rel_array[rti] = brel; - } - save_rel_array_size = subroot->simple_rel_array_size; - save_rel_array = subroot->simple_rel_array; - save_append_rel_array = subroot->append_rel_array; - - /* Make sure any initplans from this rel get into the outer list */ - root->init_plans = subroot->init_plans; + Assert(!IS_DUMMY_PATH(subpath)); /* Build list of sub-paths */ subpaths = lappend(subpaths, subpath); @@ -1587,36 +1393,6 @@ inheritance_planner(PlannerInfo *root) */ /* - * If we managed to exclude every child rel, return a dummy plan; it - * doesn't even need a ModifyTable node. - */ - if (subpaths == NIL) - { - set_dummy_rel_pathlist(final_rel); - return; - } - - /* - * Put back the final adjusted rtable into the master copy of the Query. - * (We mustn't do this if we found no non-excluded children.) - */ - parse->rtable = final_rtable; - root->simple_rel_array_size = save_rel_array_size; - root->simple_rel_array = save_rel_array; - root->append_rel_array = save_append_rel_array; - - /* Must reconstruct master's simple_rte_array, too */ - root->simple_rte_array = (RangeTblEntry **) - palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *)); - rti = 1; - foreach(lc, final_rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - root->simple_rte_array[rti++] = rte; - } - - /* * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will * have dealt with fetching non-locked marked rows, else we need to have * ModifyTable do that. @@ -1656,6 +1432,12 @@ inheritance_planner(PlannerInfo *root) * (inheritance_planner will create a single ModifyTable node covering all the * target tables.) * + * If non-NULL, planned_rel is a RelOptInfo containing paths for the query's + * top-level joinrel, which the caller produced by itself. In that case, this + * function only needs to adjust the targetlist of its cheapest_total_path. + * The only caller that may pass such a RelOptInfo currently is + * inheritance_planner. + * * tuple_fraction is the fraction of tuples we expect will be retrieved. * tuple_fraction is interpreted as follows: * 0: expect all tuples to be retrieved (normal case) @@ -1674,6 +1456,7 @@ inheritance_planner(PlannerInfo *root) */ static void grouping_planner(PlannerInfo *root, bool inheritance_update, + RelOptInfo *planned_rel, double tuple_fraction) { Query *parse = root->parse; @@ -1686,7 +1469,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, List *final_targets; List *final_targets_contain_srfs; bool final_target_parallel_safe; - RelOptInfo *current_rel; + RelOptInfo *current_rel = planned_rel; RelOptInfo *final_rel; ListCell *lc; @@ -1726,6 +1509,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * special work for recursive unions is the responsibility of * plan_set_operations. */ + Assert(current_rel == NULL); current_rel = plan_set_operations(root); /* @@ -1815,17 +1599,26 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, parse->groupClause = preprocess_groupclause(root, NIL); } - /* Preprocess targetlist */ - tlist = preprocess_targetlist(root); - /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation so - * that we can transfer its decoration (resnames etc) to the topmost - * tlist of the finished Plan. + * Preprocess targetlist, if needed. If the caller has already done + * query planning, root->processed_tlist already contains the desired + * targetlist. */ - root->processed_tlist = tlist; + if (planned_rel == NULL) + { + tlist = preprocess_targetlist(root); + + /* + * We are now done hacking up the query's targetlist. Most of the + * remaining planning work will be done with the PathTarget + * representation of tlists, but save aside the full representation + * so that we can transfer its decoration (resnames etc) to the + * topmost tlist of the finished Plan. + */ + root->processed_tlist = tlist; + } + else + tlist = root->processed_tlist; /* * Collect statistics about aggregates for estimating costs, and mark @@ -1905,8 +1698,9 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * We also generate (in standard_qp_callback) pathkey representations * of the query's sort clause, distinct clause, etc. */ - current_rel = query_planner(root, tlist, - standard_qp_callback, &qp_extra); + if (current_rel == NULL) + current_rel = query_planner(root, tlist, + standard_qp_callback, &qp_extra); /* * Convert the query's result tlist into PathTarget format. diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index aebe162713..8aef1f6535 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -894,7 +894,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->grouping_map = NULL; subroot->minmax_aggs = NIL; subroot->qual_security_level = 0; - subroot->inhTargetKind = INHKIND_NONE; subroot->hasRecursion = false; subroot->wt_param_id = -1; subroot->non_recursive_path = NULL; diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index ca6622ece9..7447853db9 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -18,6 +18,8 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" #include "parser/parsetree.h" #include "utils/lsyscache.h" #include "utils/rel.h" @@ -394,8 +396,164 @@ adjust_appendrel_attrs_mutator(Node *node, context->appinfos); return (Node *) phv; } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + + if (IsA(node, EquivalenceClass)) + { + EquivalenceClass *ec = (EquivalenceClass *) node; + EquivalenceClass *new_ec = makeNode(EquivalenceClass); + AppendRelInfo *appinfo = NULL; + RelOptInfo *parent_rel, + *child_rel; + ListCell *lc; + + /* + * First memcpy the existing EC to the new EC, which creates shallow + * copies of all the members and then make copies of members that + * we'll change below. + * + * XXX comment in _copyPathKey says it's OK to recycle EC + * pointers, but as long as we do the whole planning for a + * given child using a given root, copying ECs like this + * shouldn't be a problem. Maybe, the following code could + * be in _copyEquivalenceClass()? + */ + memcpy(new_ec, ec, sizeof(EquivalenceClass)); + new_ec->ec_opfamilies = list_copy(ec->ec_opfamilies); + new_ec->ec_sources = list_copy(ec->ec_sources); + new_ec->ec_derives = list_copy(ec->ec_derives); + new_ec->ec_relids = bms_copy(ec->ec_relids); + new_ec->ec_members = list_copy(ec->ec_members); + + /* + * No point in searching if parent rel not mentioned in eclass; but we + * can't tell that for sure if parent rel is itself a child. + */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + if (bms_is_member(appinfos[cnt]->parent_relid, ec->ec_relids)) + { + appinfo = appinfos[cnt]; + break; + } + } + + if (appinfo == NULL) + return (Node *) new_ec; + + parent_rel = find_base_rel(context->root, appinfo->parent_relid); + child_rel = find_base_rel(context->root, appinfo->child_relid); + if (parent_rel->reloptkind != RELOPT_BASEREL) + return (Node *) new_ec; + + /* + * Check if this EC contains an expression referencing the parent + * relation, translate it to child, and store it in place of + * the original parent expression. + */ + foreach(lc, new_ec->ec_members) + { + EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc); + + if (cur_em->em_is_const) + continue; /* ignore consts here */ + + /* Does it reference parent_rel? */ + if (bms_overlap(cur_em->em_relids, parent_rel->relids)) + { + /* Yes, generate transformed child version */ + Expr *new_expr; + Relids new_relids; + Relids new_nullable_relids; + + new_expr = (Expr *) + adjust_appendrel_attrs(context->root, + (Node *) cur_em->em_expr, + 1, &appinfo); + + /* + * Transform em_relids to match. Note we do *not* do + * pull_varnos(child_expr) here, as for example the + * transformation might have substituted a constant, but we + * don't want the child member to be marked as constant. + */ + new_relids = bms_difference(cur_em->em_relids, + parent_rel->relids); + new_relids = bms_add_members(new_relids, child_rel->relids); + + /* + * And likewise for nullable_relids. Note this code assumes + * parent and child relids are singletons. + */ + new_nullable_relids = cur_em->em_nullable_relids; + if (bms_overlap(new_nullable_relids, parent_rel->relids)) + { + new_nullable_relids = bms_difference(new_nullable_relids, + parent_rel->relids); + new_nullable_relids = bms_add_members(new_nullable_relids, + child_rel->relids); + } + + /* + * The new expression simply replaces the old parent one, and + * em_is_child is set to true so that it's recognized as such + * during child planning. + */ + lfirst(lc) = add_eq_member(new_ec, new_expr, + new_relids, new_nullable_relids, + false, cur_em->em_datatype); + + /* + * We have found and replaced the parent expression, so done + * with EC. + */ + break; + } + } + + /* + * Now fix up EC's relids set. It's OK to modify EC like this, + * because caller must have made a copy of the original EC. + * For example, see adjust_inherited_target_child_root. + */ + new_ec->ec_relids = bms_del_members(new_ec->ec_relids, + parent_rel->relids); + new_ec->ec_relids = bms_add_members(new_ec->ec_relids, + child_rel->relids); + return (Node *) new_ec; + } + /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index eaf788e578..68ec4aab6a 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -21,11 +21,14 @@ #include "miscadmin.h" #include "optimizer/appendinfo.h" #include "optimizer/inherit.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "utils/rel.h" +static PlannerInfo *create_inherited_target_child_root(PlannerInfo *root, + AppendRelInfo *appinfo); static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti); static void expand_partitioned_rtentry(PlannerInfo *root, @@ -492,3 +495,107 @@ translate_col_privs(const Bitmapset *parent_privs, return child_privs; } + +/* + * add_inherited_target_child_roots + * For each child of the query's result relation, this translates the + * original query to match the child and creates a PlannerInfo containing + * the translated query + * + * Child PlannerInfo reuses most of the parent PlannerInfo's fields unchanged, + * except unexpanded_tlist, processed_tlist, and all_baserels, all of which + * are based on the child relation. + */ +void +add_inherited_target_child_roots(PlannerInfo *root) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; + + Assert(root->inh_target_child_roots != NULL); + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + RangeTblEntry *childRTE; + PlannerInfo *subroot; + + if (appinfo->parent_relid != resultRelation) + continue; + + /* + * Create a PlannerInfo for processing this child target relation + * with. + */ + subroot = create_inherited_target_child_root(root, appinfo); + root->inh_target_child_roots[appinfo->child_relid] = subroot; + + /* + * If the child is a partitioned table, recurse to do this for its + * partitions. + */ + childRTE = root->simple_rte_array[appinfo->child_relid]; + if (childRTE->inh) + add_inherited_target_child_roots(subroot); + } +} + +/* + * create_inherited_target_child_root + * Workhorse of add_inherited_target_child_roots + */ +static PlannerInfo * +create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) +{ + PlannerInfo *subroot; + List *tlist; + + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + + /* + * Translate the original query to replace Vars of the parent table + * by the corresponding Vars of the child table and to make child the main + * target relation of the query. + */ + subroot = makeNode(PlannerInfo); + memcpy(subroot, root, sizeof(PlannerInfo)); + + /* + * Restore the original, unexpanded targetlist, that is, the one before + * preprocess_targetlist was run on the original query. We'll run + * preprocess_targetlist after translating the query and the targetlist, + * so that it is expanded according to child's tuple descriptor. + */ + root->parse->targetList = root->unexpanded_tlist; + subroot->parse = (Query *) adjust_appendrel_attrs(root, + (Node *) root->parse, + 1, &appinfo); + + /* + * Save the just translated targetlist as unexpanded_tlist in the child's + * subroot, so that this child's own children can use it. Must use copy + * because subroot->parse->targetList will be modified soon. + */ + subroot->unexpanded_tlist = list_copy(subroot->parse->targetList); + + /* + * Apply planner's expansion of targetlist, such as adding various junk + * column, filling placeholder entries for dropped columns, etc., all of + * which occurs with the child's TupleDesc. + */ + tlist = preprocess_targetlist(subroot); + subroot->processed_tlist = tlist; + + /* Add any newly added Vars to the child RelOptInfo. */ + build_base_rel_tlists(subroot, tlist); + + /* + * Adjust all_baserels to replace the original target relation with the + * child target relation. Copy it before modifying though. + */ + subroot->all_baserels = adjust_child_relids(subroot->all_baserels, + 1, &appinfo); + + return subroot; +} diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 3efa1bdc1a..f2ee83c81b 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1264,36 +1264,6 @@ get_relation_constraints(PlannerInfo *root, } } - /* - * Append partition predicates, if any. - * - * For selects, partition pruning uses the parent table's partition bound - * descriptor, instead of constraint exclusion which is driven by the - * individual partition's partition constraint. - */ - if (enable_partition_pruning && root->parse->commandType != CMD_SELECT) - { - List *pcqual = RelationGetPartitionQual(relation); - - if (pcqual) - { - /* - * Run the partition quals through const-simplification similar to - * check constraints. We skip canonicalize_qual, though, because - * partition quals should be in canonical form already; also, - * since the qual is in implicit-AND format, we'd have to - * explicitly convert it to explicit-AND format and back again. - */ - pcqual = (List *) eval_const_expressions(root, (Node *) pcqual); - - /* Fix Vars to have the desired varno */ - if (varno != 1) - ChangeVarNodes((Node *) pcqual, 1, varno, 0); - - result = list_concat(result, pcqual); - } - } - table_close(relation, NoLock); return result; @@ -1417,40 +1387,15 @@ relation_excluded_by_constraints(PlannerInfo *root, /* * Skip further tests, depending on constraint_exclusion. */ - switch (constraint_exclusion) - { - case CONSTRAINT_EXCLUSION_OFF: - - /* - * Don't prune if feature turned off -- except if the relation is - * a partition. While partprune.c-style partition pruning is not - * yet in use for all cases (update/delete is not handled), it - * would be a UI horror to use different user-visible controls - * depending on such a volatile implementation detail. Therefore, - * for partitioned tables we use enable_partition_pruning to - * control this behavior. - */ - if (root->inhTargetKind == INHKIND_PARTITIONED) - break; - return false; - - case CONSTRAINT_EXCLUSION_PARTITION: - - /* - * When constraint_exclusion is set to 'partition' we only handle - * OTHER_MEMBER_RELs, or BASERELs in cases where the result target - * is an inheritance parent or a partitioned table. - */ - if ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) && - !(rel->reloptkind == RELOPT_BASEREL && - root->inhTargetKind != INHKIND_NONE && - rel->relid == root->parse->resultRelation)) - return false; - break; - - case CONSTRAINT_EXCLUSION_ON: - break; /* always try to exclude */ - } + if (constraint_exclusion == CONSTRAINT_EXCLUSION_OFF) + return false; + /* + * When constraint_exclusion is set to 'partition' we only handle + * OTHER_MEMBER_RELs. + */ + else if (constraint_exclusion == CONSTRAINT_EXCLUSION_PARTITION && + rel->reloptkind != RELOPT_OTHER_MEMBER_REL) + return false; /* * Check for self-contradictory restriction clauses. We dare not make diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index d3c477a542..b46947ffe9 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -325,9 +325,6 @@ struct PlannerInfo Index qual_security_level; /* minimum security_level for quals */ /* Note: qual_security_level is zero if there are no securityQuals */ - InheritanceKind inhTargetKind; /* indicates if the target relation is an - * inheritance child or partition or a - * partitioned table */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */ bool hasHavingQual; /* true if havingQual was non-null */ @@ -348,9 +345,45 @@ struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* + * The following fields are set during query planning portion of an + * inherited UPDATE/DELETE operation. + */ + bool inherited_update; /* UPDATE/DELETE on inheritance parent? */ + + /* + * This stores the original version of the query's targetlist that's + * not modified by the planner. + */ + List *unexpanded_tlist; + + /* + * Array containing simple_rel_array_size elements, indexed by rangetable + * index (entry 0 is wasted like simple_rel_array). Only elements + * corresponding to individual inheritance child target relations are + * non-NULL. Content of each PlannerInfo is mostly same as the parent + * PlannerInfo, except for few fields such as the parse tree which is + * a translated copy of the parent's parse tree, EC list which contain + * child member expressions, etc. + */ + struct PlannerInfo **inh_target_child_roots; + + /* List of RT indexes of child target relations. */ + List *inh_target_child_rels; + + /* + * RelOptInfos corresponding to each child target rel. For leaf children, + * it's the RelOptInfo representing the output of make_rel_from_joinlist() + * called with the parent rel in the original join tree replaced by a + * given leaf child. For non-leaf children, it's the baserel RelOptInfo + * itself, left as a placeholder. + */ + List *inh_target_child_path_rels; }; + /* * In places where it's known that simple_rte_array[] must have been prepared * already, we just index into it to fetch RTEs. In code that might be diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h index d2418f15cf..4f06cdd322 100644 --- a/src/include/optimizer/inherit.h +++ b/src/include/optimizer/inherit.h @@ -18,5 +18,6 @@ extern void expand_inherited_tables(PlannerInfo *root); +extern void add_inherited_target_child_roots(PlannerInfo *root); #endif /* INHERIT_H */ diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 1b02b3b889..749c1f7f3d 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -132,6 +132,9 @@ typedef bool (*ec_matches_callback_type) (PlannerInfo *root, extern bool process_equivalence(PlannerInfo *root, RestrictInfo **p_restrictinfo, bool below_outer_join); +extern EquivalenceMember *add_eq_member(EquivalenceClass *ec, + Expr *expr, Relids relids, Relids nullable_relids, + bool is_child, Oid datatype); extern Expr *canonicalize_ec_expression(Expr *expr, Oid req_type, Oid req_collation); extern void reconsider_outer_join_clauses(PlannerInfo *root); diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index c55de5d476..c9f02c1fb0 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1780,7 +1780,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_1 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_1 -> Nested Loop Semi Join @@ -1788,7 +1788,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_2 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_2 (28 rows) -- 2.11.0
v20-0002-Lazy-creation-of-RTEs-for-inheritance-children.patch (text/plain, 94.2K) download From dc575f0e3054fd15ca762476949f2d1991d9e976 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 26 Oct 2018 16:45:59 +0900 Subject: [PATCH v20 2/4] Lazy creation of RTEs for inheritance children Currently, expand_inherited_tables called from subquery_planner opens all inheritance child tables and adds them to the query's range table and PlannerInfo in the form of AppendRelInfo's initially and later RelOptInfo's. For partitioning, that's pretty wasteful, because it's possible to determine only the partitions that will need to be scanned using partition pruning, which doesn't require opening the partitions themselves. However, the point at which expand_inherited_tables is called currently is too early to perform partition pruning. This commit rearranges things within the planner so that expand_inherited_tables can be called from query_planner after the latter has finished setting up the information needed to perform partition pruning. However that means the PlannerInfo arrays that store RangeTblEntry's, RelOptInfo's, and AppendRelInfo's need to be expanded if inheritance expansion leads to adding more relations to planning. There are also consequences around targetlist expansion. For example, preprocess_targetlist cannot add the junk columns entries needed for row mark handling until all of the inheritance children have been determined, because different children may need different types of junk columns to be added. So, the approach of late inheritance expansion required some interface tweaks for preprocess_targetlist(). For partitioning, although we don't create a RangeTblEntry and RelOptInfo for pruned partitions at make_one_rel time, partitionwise join code relies on the fact that even though partitions may have been pruned, they'd still own a RelOptInfo to handle the outer join case where the pruned partition appears on the nullable side of join. Partitionwise join code deals with that by allocating dummy RelOptInfos for pruned partitions that are based mostly on their parent's properties. There are some regression test diffs: 1. Caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child, as seen in the partition_aggregate.out test output. 2. Those in postgres_fdw.out are caused by the fact that junk columns required for row marking are added to reltarget->exprs later than user columns, because the row marking junk columns aren't added until the inheritance is expanded which as of this commit is later than it used to be as noted above. --- contrib/postgres_fdw/expected/postgres_fdw.out | 32 +- src/backend/nodes/outfuncs.c | 1 + src/backend/optimizer/path/allpaths.c | 239 +----- src/backend/optimizer/path/joinrels.c | 61 ++ src/backend/optimizer/plan/initsplan.c | 49 -- src/backend/optimizer/plan/planmain.c | 10 + src/backend/optimizer/plan/planner.c | 62 +- src/backend/optimizer/plan/setrefs.c | 6 + src/backend/optimizer/prep/preptlist.c | 15 +- src/backend/optimizer/prep/prepunion.c | 3 + src/backend/optimizer/util/appendinfo.c | 45 +- src/backend/optimizer/util/inherit.c | 843 ++++++++++++++++------ src/backend/optimizer/util/plancat.c | 47 +- src/backend/optimizer/util/relnode.c | 107 +-- src/backend/partitioning/partprune.c | 44 +- src/include/nodes/pathnodes.h | 19 +- src/include/optimizer/appendinfo.h | 7 +- src/include/optimizer/pathnode.h | 1 + src/include/optimizer/plancat.h | 4 +- src/include/optimizer/prep.h | 3 +- src/test/regress/expected/partition_aggregate.out | 4 +- 21 files changed, 949 insertions(+), 653 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index b3894d0760..fde1a9cdca 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7072,15 +7072,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7110,15 +7110,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7147,15 +7147,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid @@ -7165,15 +7165,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (39 rows) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 2527bc552e..3db2898de7 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2271,6 +2271,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_BOOL_FIELD(consider_partitionwise_join); WRITE_BITMAPSET_FIELD(top_parent_relids); WRITE_NODE_FIELD(partitioned_child_rels); + WRITE_UINT_FIELD(inh_root_parent); } static void diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index f07db81a80..994cca7110 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -45,7 +45,6 @@ #include "optimizer/tlist.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -145,9 +144,6 @@ static void subquery_push_qual(Query *subquery, static void recurse_push_qual(Node *setOp, Query *topquery, RangeTblEntry *rte, Index rti, Node *qual); static void remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel); -static bool apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, - RelOptInfo *childrel, - RangeTblEntry *childRTE, AppendRelInfo *appinfo); /* @@ -388,6 +384,14 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, else if (rte->inh) { /* + * expand_inherited_tables may have proved that the relation is empty. + * For example, if it's a partitioned table with 0 partitions or all + * of its partitions are pruned. In that case nothing to do here. + */ + if (IS_DUMMY_REL(rel)) + return; + + /* * If it's a target relation, set the sizes of children instead. * Otherwise, we'll append the outputs of children, so process it as * an "append relation". @@ -968,29 +972,12 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, int parentRTindex = rti; bool has_live_children = false; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); Assert(IS_SIMPLE_REL(rel)); - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - foreach(l, root->append_rel_list) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); @@ -1001,7 +988,6 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, ListCell *lc; List *translated_exprs, *child_target_exprs; - bool childpruned; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1009,32 +995,23 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); + Assert(childrel != NULL); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - childpruned = (did_pruning && - !bms_is_member(appinfo->child_relid, live_children)); - /* - * Unless the child is pruned, 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 baserestrictinfo quals. + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals or quals that contradict its + * constraints. */ - if (childpruned || - !apply_child_basequals(root, rel, childrel, childRTE, appinfo) || - relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* This partition needn't be scanned; skip it. */ - set_dummy_rel_pathlist(childrel); + if (IS_DUMMY_REL(childrel)) continue; - } /* * Add missing Vars to child's reltarget. @@ -1192,8 +1169,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -1201,32 +1176,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Assert(IS_SIMPLE_REL(rel)); /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, we will bubble up - * the indexes of partitioned relations that appear down in the tree, so - * that when we've created Paths for all the children, the root - * partitioned table's list will contain all such indexes. - */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - rel->partitioned_child_rels = list_make1_int(rti); - - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - - /* * If this is a partitioned baserel, set the consider_partitionwise_join * flag; currently, we only consider partitionwise joins with the baserel * if its targetlist doesn't contain a whole-row Var. @@ -1265,7 +1214,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, RelOptInfo *childrel; ListCell *parentvars; ListCell *childvars; - bool childpruned; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1273,34 +1221,26 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); + Assert(childrel != NULL); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - childpruned = (did_pruning && - !bms_is_member(appinfo->child_relid, live_children)); /* - * Unless the child is pruned, 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 baserestrictinfo quals. + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals or quals that contradict its + * constraints. */ - if (childpruned || - !apply_child_basequals(root, rel, childrel, childRTE, appinfo) || - relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* This partition needn't be scanned; skip it. */ - set_dummy_rel_pathlist(childrel); + if (IS_DUMMY_REL(childrel)) continue; - } /* - * CE failed, so finish copying/modifying targetlist and join quals. + * Copy/Modify targetlist. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -2837,6 +2777,7 @@ inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) continue; childrel = find_base_rel(root, appinfo->child_relid); + Assert(childrel != NULL); /* Ignore excluded/pruned children. */ if (IS_DUMMY_REL(childrel)) @@ -3941,134 +3882,6 @@ generate_partitionwise_join_paths(PlannerInfo *root, RelOptInfo *rel) list_free(live_children); } -/* - * apply_child_basequals - * Populate childrel's quals based on rel's quals, translating them using - * appinfo. - * - * If any of the resulting clauses evaluate to false or NULL, we return false - * and don't apply any quals. Caller can mark the relation as a dummy rel in - * this case, since it needn't be scanned. - * - * If any resulting clauses evaluate to true, they're unnecessary and we don't - * apply then. - */ -static bool -apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, - RelOptInfo *childrel, RangeTblEntry *childRTE, - AppendRelInfo *appinfo) -{ - List *childquals; - Index cq_min_security; - ListCell *lc; - - /* - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities for - * const-simplification, and perhaps even pseudoconstant quals. Therefore, - * transform each RestrictInfo separately to see if it reduces to a - * constant or pseudoconstant. (We must process them separately to keep - * track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - return false; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. (Currently - * this can only happen in appendrels originating from UNION ALL; - * inheritance child tables don't have their own securityQuals, see - * expand_inherited_rtentry().) Pull any such securityQuals up into the - * baserestrictinfo for the child. This is similar to - * process_security_barrier_quals() for the parent rel, except that we - * can't make any general deductions from such quals, since they don't - * hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - return true; -} - /***************************************************************************** * DEBUG SUPPORT *****************************************************************************/ diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index a02321486f..6f77d2a0f4 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -21,6 +21,7 @@ #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" +#include "optimizer/tlist.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -52,6 +53,9 @@ static SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, Relids left_relids, Relids right_relids); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + int partidx); /* @@ -1384,6 +1388,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (IS_SIMPLE_REL(rel1) && child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts); + if (IS_SIMPLE_REL(rel1) && child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts); + /* * If a child table has consider_partitionwise_join=false, it means * that it's a dummy relation for which we skipped setting up tlist @@ -1680,3 +1689,55 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +static RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx) +{ + RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + AppendRelInfo *appinfo = make_append_rel_info(parent, parentrte, + parent->tupdesc, + parentrte->relid, + parent->reltype, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 2afc3f1dfe..5e8a8b1eee 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -419,7 +419,6 @@ void create_lateral_join_info(PlannerInfo *root) { bool found_laterals = false; - Relids prev_parents PG_USED_FOR_ASSERTS_ONLY = NULL; Index rti; ListCell *lc; @@ -618,54 +617,6 @@ create_lateral_join_info(PlannerInfo *root) bms_add_member(brel2->lateral_referencers, rti); } } - - /* - * Lastly, propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - * - * It's possible for child rels to have their own children, in which case - * the topmost parent's lateral info must be propagated all the way down. - * This code handles that case correctly so long as append_rel_list has - * entries for child relationships before grandchild relationships, which - * is an okay assumption right now, but we'll need to be careful to - * preserve it. The assertions below check for incorrect ordering. - */ - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); - RelOptInfo *parentrel = root->simple_rel_array[appinfo->parent_relid]; - RelOptInfo *childrel = root->simple_rel_array[appinfo->child_relid]; - - /* - * If we're processing a subquery of a query with inherited target rel - * (cf. inheritance_planner), append_rel_list may contain entries for - * tables that are not part of the current subquery and hence have no - * RelOptInfo. Ignore them. We can ignore dead rels, too. - */ - if (parentrel == NULL || !IS_SIMPLE_REL(parentrel)) - continue; - - /* Verify that children are processed before grandchildren */ -#ifdef USE_ASSERT_CHECKING - prev_parents = bms_add_member(prev_parents, appinfo->parent_relid); - Assert(!bms_is_member(appinfo->child_relid, prev_parents)); -#endif - - /* OK, propagate info down */ - Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - Assert(childrel->direct_lateral_relids == NULL); - childrel->direct_lateral_relids = parentrel->direct_lateral_relids; - Assert(childrel->lateral_relids == NULL); - childrel->lateral_relids = parentrel->lateral_relids; - Assert(childrel->lateral_referencers == NULL); - childrel->lateral_referencers = parentrel->lateral_referencers; - } } diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 1cec292907..77e8ba263c 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -283,6 +283,16 @@ query_planner(PlannerInfo *root, List *tlist, } /* + * Expand RT entries that represent inherited or partitioned tables. + * This will perform partition pruning on partitioned tables in the + * original range table and also recursively on any child partitioned + * tables that were added by the expansion of the original parent(s). + * As new entries are added to the range table, various arrays in the + * PlannerInfo will be expanded accordingly. + */ + expand_inherited_tables(root); + + /* * The "actual" inheritance situation of the relations must be final at * this point. Good time to set whether the query is an inherited * UPDATE/DELETE. diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 0e97ac704a..9f3cfd7f63 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -25,6 +25,7 @@ #include "access/table.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -716,27 +717,24 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } /* * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). + * pullup (so that all non-inherited RTEs are present). */ preprocess_rowmarks(root); /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain RTE_RELATION entries, so it's OK (and marginally more efficient) - * to do it after checking for joins and other special RTEs. We must do - * this after pulling up subqueries, else we'd fail to handle inherited - * tables in subqueries. - */ - expand_inherited_tables(root); - - /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -1249,7 +1247,7 @@ inheritance_planner(PlannerInfo *root) root->unexpanded_tlist = list_copy(root->parse->targetList); /* We haven't expanded inheritance yet, so pass false. */ - tlist = preprocess_targetlist(root); + tlist = preprocess_targetlist(root, false); root->processed_tlist = tlist; qp_extra.tlist = tlist; qp_extra.activeWindows = NIL; @@ -1259,9 +1257,11 @@ inheritance_planner(PlannerInfo *root) /* * If it turned out during query planning that all the children are dummy * (pruned or excluded by constraints), no need to do the steps below. - * Let grouping_planner finish up the final path. + * For regular inheritance case, it's possible that we only need to modify + * the parent table. Let grouping_planner finish up the final path. */ - if (IS_DUMMY_REL(planned_rel)) + parent_rte = planner_rt_fetch(top_parentRTindex, root); + if (!parent_rte->inh || IS_DUMMY_REL(planned_rel)) { grouping_planner(root, false, planned_rel, 0.0); return; @@ -1277,7 +1277,6 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = planner_rt_fetch(top_parentRTindex, root); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1606,14 +1605,19 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, */ if (planned_rel == NULL) { - tlist = preprocess_targetlist(root); + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root, false); /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation - * so that we can transfer its decoration (resnames etc) to the - * topmost tlist of the finished Plan. + * We are now mostly done hacking up the query's targetlist. More + * columns might be added during query_planner after inheritance + * expansion in make_one_rel, because some children may require + * different column than the parent for row mark handling; see + * add_rowmark_junk_columns(). Most of the remaining planning + * work will be done with the PathTarget representation of tlists, + * but save aside the full representation so that we can transfer + * its decoration (resnames etc) to the topmost tlist of the + * finished Plan. */ root->processed_tlist = tlist; } @@ -2389,7 +2393,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2414,7 +2418,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } @@ -6851,6 +6855,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -6952,6 +6960,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 0213a37670..154ccda432 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -326,6 +326,12 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) UPPERREL_FINAL, NULL))) add_rtes_to_flat_rtable(rel->subroot, true); } + /* + * A NULL rel also means an unplanned subquery rte, so apply + * flatten_unplanned_rtes. + */ + else + flatten_unplanned_rtes(glob, rte); } rti++; } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 5392d1a561..41a57d16b2 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -65,9 +65,13 @@ static List *expand_targetlist(List *tlist, int command_type, * * As a side effect, if there's an ON CONFLICT UPDATE clause, its targetlist * is also preprocessed (and updated in-place). + * + * inheritance_expanded specifies whether inheritance tables are expanded + * as of calling this function. Caller must specify it because that affects + * which row marking related junk columns get added to the targetlist. */ List * -preprocess_targetlist(PlannerInfo *root) +preprocess_targetlist(PlannerInfo *root, bool inheritance_expanded) { Query *parse = root->parse; int result_relation = parse->resultRelation; @@ -134,6 +138,15 @@ preprocess_targetlist(PlannerInfo *root) if (rc->rti != rc->prti) continue; + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which will change the parent row + * mark's allMarkTypes fields. + */ + if (rc->isParent && !inheritance_expanded) + continue; + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) { /* Need to fetch TID */ diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 55eeb5127c..7bed914f35 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -31,12 +31,15 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" #include "optimizer/cost.h" +#include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index 7447853db9..c72251aea2 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -33,10 +33,10 @@ typedef struct AppendRelInfo **appinfos; } adjust_appendrel_attrs_context; -static void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, adjust_appendrel_attrs_context *context); static List *adjust_inherited_tlist(List *tlist, @@ -48,18 +48,20 @@ static List *adjust_inherited_tlist(List *tlist, * Build an AppendRelInfo for the parent-child pair */ AppendRelInfo * -make_append_rel_info(Relation parentrel, Relation childrel, - Index parentRTindex, Index childRTindex) +make_append_rel_info(RelOptInfo *parent, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childtype, + Index childRTindex) { AppendRelInfo *appinfo = makeNode(AppendRelInfo); - appinfo->parent_relid = parentRTindex; + appinfo->parent_relid = parent->relid; appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->rd_rel->reltype; - appinfo->child_reltype = childrel->rd_rel->reltype; - make_inh_translation_list(parentrel, childrel, childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = RelationGetRelid(parentrel); + appinfo->parent_reltype = parent->reltype; + appinfo->child_reltype = childtype; + make_inh_translation_list(parent->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; return appinfo; } @@ -72,14 +74,11 @@ make_append_rel_info(Relation parentrel, Relation childrel, * For paranoia's sake, we match type/collation as well as attribute name. */ static void -make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, - List **translated_vars) +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) { List *vars = NIL; - TupleDesc old_tupdesc = RelationGetDescr(oldrelation); - TupleDesc new_tupdesc = RelationGetDescr(newrelation); - Oid new_relid = RelationGetRelid(newrelation); int oldnatts = old_tupdesc->natts; int newnatts = new_tupdesc->natts; int old_attno; @@ -109,7 +108,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, * When we are generating the "translation list" for the parent table * of an inheritance set, no need to search for matches. */ - if (oldrelation == newrelation) + if (from_rel == to_rel) { vars = lappend(vars, makeVar(newvarno, (AttrNumber) (old_attno + 1), @@ -135,10 +134,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, { HeapTuple newtup; - newtup = SearchSysCacheAttName(new_relid, attname); + newtup = SearchSysCacheAttName(to_rel, attname); if (!newtup) elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; ReleaseSysCache(newtup); @@ -148,10 +147,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, /* Found it, check type and collation match */ if (atttypid != att->atttypid || atttypmod != att->atttypmod) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); if (attcollation != att->attcollation) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); vars = lappend(vars, makeVar(newvarno, (AttrNumber) (new_attno + 1), diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 68ec4aab6a..579286e54c 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -18,33 +18,47 @@ #include "access/table.h" #include "catalog/partition.h" #include "catalog/pg_inherits.h" +#include "catalog/pg_type.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" #include "optimizer/inherit.h" +#include "optimizer/optimizer.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" +#include "optimizer/plancat.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" +#include "partitioning/partprune.h" #include "utils/rel.h" static PlannerInfo *create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo); -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); +static void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti); +static void expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, + RangeTblEntry *rte, Index rti, RelOptInfo *rel); static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); - +static RelOptInfo *build_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); +static bool apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *childrel, + RangeTblEntry *childRTE, AppendRelInfo *appinfo); /* * expand_inherited_tables @@ -52,37 +66,145 @@ static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, * into an "append relation". At the conclusion of this process, * the "inh" flag is set in all and only those RTEs that are append * relation parents. + * + * Note that although we're calling the combined output of inheritance set + * relations an "append relation" here, the caller may not always want to + * combine the relations. For example, if the parent of the inheritance + * set is the query's target relation, each child relation is processed + * on its own as the query's target relation. */ void expand_inherited_tables(PlannerInfo *root) { - Index nrtes; + int orig_rtable_size; Index rti; - ListCell *rl; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; /* * expand_inherited_rtentry may add RTEs to parse->rtable. The function is * expected to recursively handle any RTEs that it creates with inh=true. * So just scan as far as the original end of the rtable list. */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) + for (rti = 1; rti < orig_rtable_size; rti++) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + if (rte->inh) + expand_inherited_rtentry(root, brel, rte, rti); } } /* * expand_inherited_rtentry - * Check whether a rangetable entry represents an inheritance set. - * If so, add entries for all the child tables to the query's - * rangetable, and build AppendRelInfo nodes for all the child tables - * and add them to root->append_rel_list. If not, clear the entry's - * "inh" flag to prevent later code from looking for AppendRelInfos. + * This initializes RelOptInfos for inheritance child relations if the + * passed-in relation has any + * + * 'rel' is the parent relation, whose range table entry ('rte') has been + * marked to require adding children. Parent could either be a subquery (if + * we flattened UNION ALL query) or a table that's known to have (or once had) + * inheritance children. The latter consists of both regular inheritance + * parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as a child to be scanned as part of the + * inheritance set. The original RTE is considered to represent the whole + * inheritance set. + */ +static void +expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_inheritance_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_nonpartitioned_inherited_rtentry(root, rte, rti, rel); + } +} + +/* + * expand_nonpartitioned_inherited_rtentry + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. * * Note that the original RTE is considered to represent the whole * inheritance set. The first of the generated RTEs is an RTE for the same @@ -93,55 +215,35 @@ expand_inherited_tables(PlannerInfo *root) * regular inheritance, a parent RTE must always have at least two associated * AppendRelInfos: one corresponding to the parent table as a simple member of * inheritance set and one or more corresponding to the actual children. - * Since a partitioned table is not scanned, it might have only one associated - * AppendRelInfo. */ static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, + RangeTblEntry *rte, + Index rti, + RelOptInfo *rel) { Oid parentOID; PlanRowMark *oldrc; - Relation oldrelation; - LOCKMODE lockmode; + LOCKMODE lockmode = rte->rellockmode; List *inhOIDs; ListCell *l; + int num_children; + int num_children_added = 0; - /* Does RT entry allow inheritance? */ - if (!rte->inh) - return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } - /* Fast path for common case of childless table */ + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); parentOID = rte->relid; - if (!has_subclass(parentOID)) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * The rewriter should already have obtained an appropriate lock on each - * relation named in the query. However, for each child relation we add - * to the query, we must obtain an appropriate lock, because this will be - * the first use of those relations in the parse/rewrite/plan pipeline. - * Child rels should use the same lockmode as their parent. - */ - lockmode = rte->rellockmode; /* Scan for all members of inheritance set, acquire needed locks */ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); /* * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite above has_subclass() check, if table - * once had a child but no longer does. + * case. This could happen despite has_subclass() check performed by + * subquery_planner, if table once had a child but no longer does. */ - if (list_length(inhOIDs) < 2) + num_children = list_length(inhOIDs); + if (num_children < 2) { /* Clear flag before returning */ rte->inh = false; @@ -149,216 +251,221 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } /* - * If parent relation is selected FOR UPDATE/SHARE, we need to mark its - * PlanRowMark as isParent = true, and generate a new PlanRowMark for each - * child. + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. */ oldrc = get_plan_rowmark(root->rowMarks, rti); - if (oldrc) - oldrc->isParent = true; + Assert(oldrc == NULL || oldrc->isParent); /* - * Must open the parent relation to examine its tupdesc. We need not lock - * it; we assume the rewriter already did. + * Must expand PlannerInfo arrays by num_children before we can add + * children. */ - oldrelation = table_open(parentOID, NoLock); + Assert(num_children > 0); + expand_planner_arrays(root, num_children); - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) + foreach(l, inhOIDs) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); - - /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. - */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); - } - else - { - List *appinfos = NIL; + Oid childOID = lfirst_oid(l); + Relation newrelation; RangeTblEntry *childrte; Index childRTindex; + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. */ - foreach(l, inhOIDs) + if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = table_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - table_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - table_close(newrelation, NoLock); + table_close(newrelation, lockmode); + continue; } - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); + (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation, &childrte, + &childRTindex); + Assert(childrte != NULL); + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + Assert(childRTindex > 0); + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; } - table_close(oldrelation, NoLock); + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } } /* * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) + Index parentRTindex, RelOptInfo *parentrel) { + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; - check_stack_depth(); - - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + if (bms_num_members(partindexes) > 0) + expand_planner_arrays(root, bms_num_members(partindexes)); /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. */ - if (partdesc->nparts == 0) - { - parentrte->inh = false; - return; - } + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); - for (i = 0; i < partdesc->nparts; i++) + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) { - Oid childOID = partdesc->oids[i]; - Relation childrel; + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + RangeTblEntry *childrte; + Index childRTindex; /* Open rel; we already have required locks */ - childrel = table_open(childOID, NoLock); + newrelation = table_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation, + &childrte, &childRTindex); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + /* Close child relations, but keep locks */ + table_close(newrelation, NoLock); + + /* If the child is partitioned itself, expand it too. */ + if (childrel->part_scheme) + { + Assert(childrte->inh); expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); + childrel); + } + } - /* Close child relation, but keep locks */ - table_close(childrel, NoLock); + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); } } /* - * expand_single_inheritance_child - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) + * The return value is the RelOptInfo that's added. * * PlanRowMarks still carry the top-parent's RTI, and the top-parent's * allMarkTypes field still accumulates values from all descendents. * * "parentrte" and "parentRTindex" are immediate parent's RTE and * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. */ -static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); Oid childOID = RelationGetRelid(childrel); RangeTblEntry *childrte; Index childRTindex; AppendRelInfo *appinfo; + RelOptInfo *childrelopt; /* * Build an RTE for the child, and attach to query's rangetable list. We * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh = false. Also, set requiredPerms to zero since - * all required permissions checks are done on the original RTE. Likewise, - * set the child's securityQuals to empty, because we only want to apply - * the parent's RLS conditions regardless of what RLS properties + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties * individual children may have. (This is an intentional choice to make * inherited RLS work like regular permissions checks.) The parent * securityQuals will be propagated to children along with other base @@ -368,49 +475,42 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childrte_p = childrte; childrte->relid = childOID; childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) - childrte->inh = true; - else - childrte->inh = false; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); childrte->requiredPerms = 0; childrte->securityQuals = NIL; parse->rtable = lappend(parse->rtable, childrte); childRTindex = list_length(parse->rtable); *childRTindex_p = childRTindex; - /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. - */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) - { - appinfo = make_append_rel_info(parentrel, childrel, - parentRTindex, childRTindex); - *appinfos = lappend(*appinfos, appinfo); + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + RelationGetDescr(childrel), + RelationGetRelid(childrel), + RelationGetForm(childrel)->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); } /* @@ -442,6 +542,147 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need to create one, + * because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_inheritance_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; +} + +/* + * build_inheritance_child_rel + * Build a RelOptInfo for child relation of an inheritance set + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields based on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turn out to be self-contradictory, + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + * + * It's possible for child rels to have their own children, in which case + * the topmost parent's lateral info must be propagated all the way down. + * That's ensured by having childrel be expanded via this same path. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + */ + if (!apply_child_basequals(root, parent, childrel, childRTE, appinfo) || + relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; } /* @@ -584,7 +825,7 @@ create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) * column, filling placeholder entries for dropped columns, etc., all of * which occurs with the child's TupleDesc. */ - tlist = preprocess_targetlist(subroot); + tlist = preprocess_targetlist(subroot, true); subroot->processed_tlist = tlist; /* Add any newly added Vars to the child RelOptInfo. */ @@ -599,3 +840,131 @@ create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) return subroot; } + +/* + * apply_child_basequals + * Populate childrel's quals based on rel's quals, translating them using + * appinfo. + * + * If any of the resulting clauses evaluate to false or NULL, we return false + * and don't apply any quals. Caller can mark the relation as a dummy rel in + * this case, since it needn't be scanned. + * + * If any resulting clauses evaluate to true, they're unnecessary and we don't + * apply then. + */ +static bool +apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *childrel, RangeTblEntry *childRTE, + AppendRelInfo *appinfo) +{ + List *childquals; + Index cq_min_security; + ListCell *lc; + + /* + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities for + * const-simplification, and perhaps even pseudoconstant quals. Therefore, + * transform each RestrictInfo separately to see if it reduces to a + * constant or pseudoconstant. (We must process them separately to keep + * track of the security level of each qual.) + */ + childquals = NIL; + cq_min_security = UINT_MAX; + foreach(lc, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, + (Node *) rinfo->clause, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + return false; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + /* track minimum security level among child quals */ + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might have + * securityQuals associated with this particular child node. (Currently + * this can only happen in appendrels originating from UNION ALL; + * inheritance child tables don't have their own securityQuals, see + * expand_inherited_rtentry().) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except that we + * can't make any general deductions from such quals, since they don't + * hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* not likely that we'd see constants here, so no check */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, false, false, + security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* + * OK, we've got all the baserestrictinfo quals for this child. + */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + return true; +} diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index f2ee83c81b..f3f7bb29c6 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -105,20 +105,20 @@ static void set_baserel_partition_key_exprs(Relation relation, * important for it. */ void -get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) +get_relation_info(PlannerInfo *root, RangeTblEntry *rte, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; bool hasindex; List *indexinfos = NIL; + bool inhparent = rte->inh; /* * We need not lock the relation since it was already locked, either by * the rewriter or when expand_inherited_rtentry() added it to the query's * rangetable. */ - relation = table_open(relationObjectId, NoLock); + relation = heap_open(rte->relid, NoLock); /* Temporary and unlogged relations are inaccessible during recovery. */ if (!RelationNeedsWAL(relation) && RecoveryInProgress()) @@ -444,11 +444,32 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, get_relation_foreign_keys(root, rel, relation, inhparent); /* - * Collect info about relation's partitioning scheme, if any. Only - * inheritance parents may be partitioned. + * Collect some additional information for inheritance parents. */ - if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - set_relation_partition_info(root, rel, relation); + if (inhparent) + { + /* + * We'll need the TupleDesc when initializing the child relation. + * A copy is being made because concurrent changes might drop + * the relcache entry. That's possible because ALTER TABLE + * child_table NO INHERIT parent_table only requires an + * AccessShareLock on parent_table. + */ + rel->tupdesc = CreateTupleDescCopy(RelationGetDescr(relation)); + rel->reltype = RelationGetForm(relation)->reltype; + + /* + * If partitioned, also save the information of partitioning scheme, + * and whether the query updates any of the partition key columns. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + set_relation_partition_info(root, rel, relation); + root->partColsUpdated |= has_partition_attrs(relation, + rte->updatedCols, + NULL); + } + } table_close(relation, NoLock); @@ -458,7 +479,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * removing an index, or adding a hypothetical index to the indexlist. */ if (get_relation_info_hook) - (*get_relation_info_hook) (root, relationObjectId, inhparent, rel); + (*get_relation_info_hook) (root, rte->relid, rte->inh, rel); } /* @@ -1845,16 +1866,20 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; + + /* + * Since we must've taken a lock on the table, it's okay to simply copy + * the pointers to relcache data here. + */ + rel->part_oids = partdesc->oids; + rel->boundinfo = partdesc->boundinfo; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index f04c6b76f4..09b1b8075a 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -132,6 +132,50 @@ setup_append_rel_array(PlannerInfo *root) } /* + * expand_planner_arrays + * Expand the PlannerInfo arrays by add_size members and initialize the + * the newly added bytes with zero + */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size; + + Assert(add_size > 0); + + new_size = root->simple_rel_array_size + add_size; + + /* Expand various arrays and 0-initialize added bytes. */ + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + + if (root->append_rel_array) + { + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + } + else + { + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + } + + root->simple_rel_array_size = new_size; +} + +/* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. */ @@ -195,6 +239,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->joininfo = NIL; rel->has_eclass_joins = false; rel->consider_partitionwise_join = false; /* might get changed later */ + rel->top_parent_relids = NULL; /* might be changed later */ rel->part_scheme = NULL; rel->nparts = 0; rel->boundinfo = NULL; @@ -203,6 +248,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->partexprs = NULL; rel->nullable_partexprs = NULL; rel->partitioned_child_rels = NIL; + rel->inh_root_parent = 0; /* might be changed later */ /* * Pass top parent's relids down the inheritance hierarchy. If the parent @@ -216,16 +262,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->top_parent_relids = parent->top_parent_relids; else rel->top_parent_relids = bms_copy(parent->relids); + + /* + * For inheritance child relations, we also set inh_root_parent. + * Note that 'parent' might itself be a child (a sub-partitioned + * partition), in which case we simply use its value of + * inh_root_parent. + */ + if (parent->rtekind == RTE_RELATION) + rel->inh_root_parent = parent->inh_root_parent > 0 ? + parent->inh_root_parent : + parent->relid; } - else - rel->top_parent_relids = NULL; /* Check type of rtable entry */ switch (rte->rtekind) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte, rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -273,52 +328,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); - /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. - */ - if (rte->inh) - { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); - - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; - } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); - } - return rel; } diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 8c9721935d..472a6cd331 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -45,6 +45,8 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "partitioning/partprune.h" @@ -433,17 +435,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -537,23 +545,20 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ @@ -561,6 +566,13 @@ prune_append_rel_partitions(RelOptInfo *rel) return NULL; /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. */ @@ -587,15 +599,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index b46947ffe9..1d36bda039 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -15,6 +15,7 @@ #define PATHNODES_H #include "access/sdir.h" +#include "access/tupdesc.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/params.h" @@ -383,7 +384,6 @@ struct PlannerInfo }; - /* * In places where it's known that simple_rte_array[] must have been prepared * already, we just index into it to fetch RTEs. In code that might be @@ -736,11 +736,28 @@ typedef struct RelOptInfo int nparts; /* number of partitions */ struct PartitionBoundInfoData *boundinfo; /* Partition bounds */ List *partition_qual; /* partition constraint */ + Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ + + /* + * For inheritance children, this is the RT index of inheritance table + * mentioned in the query from which this relation originated. + * top_parent_relids cannot be used for this, because if the inheritance + * root table is itself under UNION ALL, top_parent_relids contains the + * RT index of UNION ALL parent subquery. + */ + Index inh_root_parent; + + /* + * Set only if this is an inheritance parent relation. This information + * is needed when initializing the planning info for children. + */ + TupleDesc tupdesc; /* A "copy" of the table's tuple desriptor */ + Oid reltype; /* Table's reltype */ } RelOptInfo; /* diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h index 790914c1b0..c0b8db9a62 100644 --- a/src/include/optimizer/appendinfo.h +++ b/src/include/optimizer/appendinfo.h @@ -17,9 +17,10 @@ #include "nodes/pathnodes.h" #include "utils/relcache.h" -extern AppendRelInfo *make_append_rel_info(Relation parentrel, - Relation childrel, - Index parentRTindex, Index childRTindex); +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parent, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childtype, + Index childRTindex); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index d0c8f99d0a..2e9ebdc94c 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -265,6 +265,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index 40f70f9f2b..fb370ed80e 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -25,8 +25,8 @@ typedef void (*get_relation_info_hook_type) (PlannerInfo *root, extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; -extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); +extern void get_relation_info(PlannerInfo *root, RangeTblEntry *rte, + RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index a9b2c9026c..27de05ba3e 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -34,7 +34,8 @@ extern Relids get_relids_for_join(Query *query, int joinrelid); /* * prototypes for preptlist.c */ -extern List *preprocess_targetlist(PlannerInfo *root); +extern List *preprocess_targetlist(PlannerInfo *root, + bool inheritance_expanded); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 6bc106831e..1450cef057 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) -- 2.11.0
v20-0003-Teach-planner-to-only-process-unpruned-partition.patch (text/plain, 6.9K) download From 9220b361070ae138183da495235160aff55931b0 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 15 Oct 2018 10:59:24 +0900 Subject: [PATCH v20 3/4] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/inherit.c | 1 + src/backend/optimizer/util/relnode.c | 3 +++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/pathnodes.h | 4 ++++ 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 6f77d2a0f4..fdbf7689ed 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1452,6 +1452,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 9f3cfd7f63..fc86abe12d 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6847,7 +6847,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -6855,9 +6857,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -6938,7 +6938,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -6950,7 +6949,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -6960,9 +6961,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 579286e54c..b014bb4633 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -365,6 +365,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, /* Perform pruning. */ partindexes = prune_append_rel_partitions(parentrel); + parentrel->live_parts = partindexes; /* Must expand PlannerInfo arrays before we can add children. */ if (bms_num_members(partindexes) > 0) diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 09b1b8075a..cfb8077575 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1734,6 +1734,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 472a6cd331..babb7d3406 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -438,29 +438,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 1d36bda039..2571c17f65 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -739,6 +739,10 @@ typedef struct RelOptInfo Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Indexes into part_rels of the non-NULL + * RelOptInfos of unpruned partitions; exists + * to avoid having to iterate over the entire + * part_rels array to filter NULL entries. */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.11.0
v20-0004-Do-not-lock-all-partitions-at-the-beginning.patch (text/plain, 1.9K) download From 0b90794ed92f2116838bd9e25a18f18940782fe8 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 12 Oct 2018 19:12:53 +0900 Subject: [PATCH v20 4/4] Do not lock all partitions at the beginning --- src/backend/optimizer/util/inherit.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index b014bb4633..b7e1b22e00 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -332,10 +332,6 @@ expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, * * Partitions are added to the query in order in which they are found in * the parent's PartitionDesc. - * - * Note: even though only the unpruned partitions will be added to the - * resulting plan, this still locks *all* partitions via find_all_inheritors - * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, @@ -349,10 +345,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, parentrel->inh_root_parent : parentRTindex; - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - /* * Initialize partitioned_child_rels to contain this RT index. * @@ -389,8 +381,11 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, RangeTblEntry *childrte; Index childRTindex; - /* Open rel; we already have required locks */ - newrelation = table_open(childOID, NoLock); + /* + * Open rel; this's the first time of opening partitions for this + * query, so take the appropriate locks. + */ + newrelation = table_open(childOID, lockmode); Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-02-08 01:34  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
  0 siblings, 2 replies; 265+ messages in thread

From: Imai, Yoshikazu @ 2019-02-08 01:34 UTC (permalink / raw)
  To: Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Tsunakawa-san,

On Wed, Feb 6, 2019 at 2:04 AM, Tsunakawa, Takayuki wrote:
> Can you compare the performance of auto and force_custom_plan again with
> the attached patch?  It uses PGPROC's LOCALLOCK list instead of the hash
> table.

Thanks for the patch, but it seems to have some problems.
When I executed create/drop/select commands to large partitions, like over than 512 partitions, backend died unexpectedly. Since I could see the difference of the performance of auto and force_custom_plan when partitions is large, patch needs to be modified to check whether performance is improved or not.

Thanks
--
Yoshikazu Imai



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-08 01:45  David Rowley <david.rowley@2ndquadrant.com>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  1 sibling, 1 reply; 265+ messages in thread

From: David Rowley @ 2019-02-08 01:45 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On Fri, 8 Feb 2019 at 14:34, Imai, Yoshikazu
<imai.yoshikazu@jp.fujitsu.com> wrote:
>
> On Wed, Feb 6, 2019 at 2:04 AM, Tsunakawa, Takayuki wrote:
> > Can you compare the performance of auto and force_custom_plan again with
> > the attached patch?  It uses PGPROC's LOCALLOCK list instead of the hash
> > table.
>
> Thanks for the patch, but it seems to have some problems.
> When I executed create/drop/select commands to large partitions, like over than 512 partitions, backend died unexpectedly. Since I could see the difference of the performance of auto and force_custom_plan when partitions is large, patch needs to be modified to check whether performance is improved or not.

It's good to see work being done to try and improve this, but I think
it's best to do it on another thread. I think there was some agreement
upthread about this not being Amit's patch's problem. Doing it here
will make keeping track of this more complex than it needs to be.
There's also Amit's issue of keeping his patch series up to date. The
CFbot is really useful to alert patch authors when that's required,
but having other patches posted to the same thread can cause the CFbot
to check the wrong patch.

-- 
 David Rowley                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-02-08 01:50  Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
  parent: David Rowley <david.rowley@2ndquadrant.com>
  0 siblings, 1 reply; 265+ messages in thread

From: Tsunakawa, Takayuki @ 2019-02-08 01:50 UTC (permalink / raw)
  To: David Rowley <david.rowley@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

From: David Rowley [mailto:david.rowley@2ndquadrant.com]
> It's good to see work being done to try and improve this, but I think
> it's best to do it on another thread. I think there was some agreement
> upthread about this not being Amit's patch's problem. Doing it here
> will make keeping track of this more complex than it needs to be.
> There's also Amit's issue of keeping his patch series up to date. The
> CFbot is really useful to alert patch authors when that's required,
> but having other patches posted to the same thread can cause the CFbot
> to check the wrong patch.

OK, you're right.  I'll continue this on another thread.


Regards
Takayuki Tsunakawa



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-08 02:39  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
  0 siblings, 1 reply; 265+ messages in thread

From: Amit Langote @ 2019-02-08 02:39 UTC (permalink / raw)
  To: Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>; 'David Rowley' <david.rowley@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Tsunakawa-san,

On 2019/02/08 10:50, Tsunakawa, Takayuki wrote:
> From: David Rowley [mailto:david.rowley@2ndquadrant.com]
>> It's good to see work being done to try and improve this, but I think
>> it's best to do it on another thread. I think there was some agreement
>> upthread about this not being Amit's patch's problem. Doing it here
>> will make keeping track of this more complex than it needs to be.
>> There's also Amit's issue of keeping his patch series up to date. The
>> CFbot is really useful to alert patch authors when that's required,
>> but having other patches posted to the same thread can cause the CFbot
>> to check the wrong patch.
> 
> OK, you're right.  I'll continue this on another thread.

Thank you.  I do appreciate that Imai-san has persistently tried to find
interesting problems to solve beyond the patches we're working on here.
Maybe I chose the the subject line of this thread poorly when I began
working on it.  It should perhaps have been something like "speeding up
planning of point-lookup queries with many partitions" or something like
that.  There are important use cases beyond point lookup even with
partitioned tables (or maybe more so with partitioned tables), but perhaps
unsurprisingly, the bottlenecks in those cases are not *just* in the planner.

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-02-08 03:41  Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 0 replies; 265+ messages in thread

From: Tsunakawa, Takayuki @ 2019-02-08 03:41 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Hi Amit,

From: Amit Langote [mailto:Langote_Amit_f8@lab.ntt.co.jp]
> Maybe I chose the the subject line of this thread poorly when I began
> working on it.  It should perhaps have been something like "speeding up
> planning of point-lookup queries with many partitions" or something like
> that.  There are important use cases beyond point lookup even with
> partitioned tables (or maybe more so with partitioned tables), but perhaps
> unsurprisingly, the bottlenecks in those cases are not *just* in the
> planner.

No, it's simply my fault.  I wasn't aware of the CF Bot and the CF entry page that act on the latest submitted patch.  I'm relieved to see you have submitted the revised patch.


Regards
Takayuki Tsunakawa



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-02-08 04:44  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 1 reply; 265+ messages in thread

From: Imai, Yoshikazu @ 2019-02-08 04:44 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Amit Langote <amitlangote09@gmail.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Rowley <david.rowley@2ndquadrant.com>; pgsql-hackers

Amit-san,

On Thu, Feb 7, 2019 at 10:22 AM, Amit Langote wrote:
> Rebased over bdd9a99aac.

I did code review of 0001 and I have some suggestions. Could you check them?

1.
0001: line 418
+		 * add_inherit_target_child_root() would've added only those that are

add_inherit_target_child_root() doesn't exist now, so an above comment needs to be modified.

2.
0001: line 508-510

In set_inherit_target_rel_pathlists():
+	/* Nothing to do if all the children were excluded. */
+	if (IS_DUMMY_REL(rel))
+		return;

These codes aren't needed or can be replaced by Assert because set_inherit_target_rel_pathlists is only called from set_rel_pathlist which excutes IS_DUMMY_REL(rel) before calling set_inherit_target_rel_pathlists, as follows.

set_rel_pathlist(...)
{
    ...
    if (IS_DUMMY_REL(rel))
    {    
        /* We already proved the relation empty, so nothing more to do */
    }    
    else if (rte->inh)
    {    
        /*
         * If it's a target relation, set the pathlists of children instead.
         * Otherwise, we'll append the outputs of children, so process it as
         * an "append relation".
         */
        if (root->inherited_update && root->parse->resultRelation == rti) 
        {
            inherited_update = true;
            set_inherit_target_rel_pathlists(root, rel, rti, rte);

3.
0001: line 1919-1920

-		case CONSTRAINT_EXCLUSION_ON:
-			break;				/* always try to exclude */

CONSTRAINT_EXCLUSION_ON is no longer used, so should we remove it also from guc parameters?

4.
0001:

Can we remove enum InheritanceKind which is no longer used?


I also see the patch from a perspective of separating codes from 0001 which are not essential of Overhaul inheritance update/delete planning. Although almost all of codes are related each other, but I found below two things can be moved to another patch.

---
0001: line 550-608

This section seems to be just refinement of set_append_rel_size().
So can we separate this from 0001 to another patch?

---
0001: line 812-841, 940-947, 1525-1536, 1938-1947 

These codes are related to removement of InheritanceKind from relation_excluded_by_constraints(), so I think it is something like cleaning of unneeded codes. Can we separate this to patch as some-code-clearnups-of-0001.patch? Of course, we can do that only if removing of these codes from 0001 would not bother success of "make check" of 0001.
I also think that what I pointed out at above 3. and 4. can also be included in some-code-clearnups-of-0001.patch.

What do you think?


--
Yoshikazu Imai



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-02-08 06:40  Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 1 reply; 265+ messages in thread

From: Tsunakawa, Takayuki @ 2019-02-08 06:40 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Amit Langote <amitlangote09@gmail.com>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers

Hi Amit,

I'm afraid v20-0001 fails to apply to the current HEAD (precisely, ftp.postgresql.org/pub/snapshot/dev/postgresql-snapshot.tar.gz).  Could you check it?

I'm trying to reproduce what Imai-san hit with my patch.  His environment is master@Jan/28 + v18 of your patches.  When he added my patch there, CREATE TABLE crashed.

On the other hand, the current master + my patch succeeds.  So, I wanted to test with the current HEAD + v20 of your patch + my patch.


Regards
Takayuki Tsunakawa







^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-08 06:51  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
  0 siblings, 1 reply; 265+ messages in thread

From: Amit Langote @ 2019-02-08 06:51 UTC (permalink / raw)
  To: Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>; Amit Langote <amitlangote09@gmail.com>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers

Tsunakawa-san,

On 2019/02/08 15:40, Tsunakawa, Takayuki wrote:
> Hi Amit,
> 
> I'm afraid v20-0001 fails to apply to the current HEAD (precisely, ftp.postgresql.org/pub/snapshot/dev/postgresql-snapshot.tar.gz).  Could you check it?

Hmm, I had rebased v20 over HEAD as of yesterday evening.  CF bot seemed
to be happy with it too:

http://cfbot.cputube.org/amit-langote.html

Also, I rebased the patches again on the latest HEAD as of this morning
and there were no issues.

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-02-08 07:43  Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 0 replies; 265+ messages in thread

From: Tsunakawa, Takayuki @ 2019-02-08 07:43 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Amit Langote <amitlangote09@gmail.com>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; pgsql-hackers

From: Amit Langote [mailto:Langote_Amit_f8@lab.ntt.co.jp]
> Sent: Friday, February 08, 2019 3:52 PM
> Hmm, I had rebased v20 over HEAD as of yesterday evening.  CF bot seemed
> to be happy with it too:
> 
> http://cfbot.cputube.org/amit-langote.html
> 
> Also, I rebased the patches again on the latest HEAD as of this morning
> and there were no issues.

There seem to have been some modifications between the latest snapshot tarball and HEAD.  I've just managed to apply your v20 to HEAD.  Thanks.


Regards
Takayuki Tsunakawa





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-02-08 08:47  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  1 sibling, 0 replies; 265+ messages in thread

From: Imai, Yoshikazu @ 2019-02-08 08:47 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On Fri, Feb 8, 2019 at 1:34 AM, I wrote:
> On Wed, Feb 6, 2019 at 2:04 AM, Tsunakawa, Takayuki wrote:
> > Can you compare the performance of auto and force_custom_plan again
> > with the attached patch?  It uses PGPROC's LOCALLOCK list instead of
> > the hash table.
> 
> Thanks for the patch, but it seems to have some problems.

I just missed compiling.

Performance degradation I saw before is improved! The results are below.

[v20 + faster-locallock-scan.patch]
auto:   9,069 TPS
custom: 9,015 TPS

[v20]
auto:   8,037 TPS
custom: 8,933 TPS

As David and I mentioned this patch should be discussed on another thread, so Tsunakawa-san, could you launch the another thread please?

Thanks
--
Yoshikazu Imai



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-08 09:12  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  0 siblings, 2 replies; 265+ messages in thread

From: Amit Langote @ 2019-02-08 09:12 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Amit Langote <amitlangote09@gmail.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Rowley <david.rowley@2ndquadrant.com>; pgsql-hackers

Imai-san,

Thanks for the comments.

On 2019/02/08 13:44, Imai, Yoshikazu wrote:
> 3.
> 0001: line 1919-1920
> 
> -		case CONSTRAINT_EXCLUSION_ON:
> -			break;				/* always try to exclude */
> 
> CONSTRAINT_EXCLUSION_ON is no longer used, so should we remove it also from guc parameters?

Well, we haven't removed the "on" setting itself.

> 4.
> 0001:
> 
> Can we remove enum InheritanceKind which is no longer used?

That we can do.

> I also see the patch from a perspective of separating codes from 0001 which are not essential of Overhaul inheritance update/delete planning. Although almost all of codes are related each other, but I found below two things can be moved to another patch.
> 
> ---
> 0001: line 550-608
> 
> This section seems to be just refinement of set_append_rel_size().
> So can we separate this from 0001 to another patch?
> 
> ---
> 0001: line 812-841, 940-947, 1525-1536, 1938-1947 
> 
> These codes are related to removement of InheritanceKind from relation_excluded_by_constraints(), so I think it is something like cleaning of unneeded codes. Can we separate this to patch as some-code-clearnups-of-0001.patch? Of course, we can do that only if removing of these codes from 0001 would not bother success of "make check" of 0001.
> I also think that what I pointed out at above 3. and 4. can also be included in some-code-clearnups-of-0001.patch.

Okay, I've broken down those changes into separate patches, so that
cleanup hunks are not fixed with other complex changes.

0001 is now a patch to remove duplicate code from set_append_rel_size.  It
combines multiple blocks that have the same body doing
set_dummy_rel_pathlist().

0002 is the "overhaul inherited update/delete planning"

0003 is a cleanup patch that gets rid of some code that is rendered
useless due to 0002 (partitioned tables no longer use constraint exclusion)

I think 0001 can be committed on its own.  0002+0003 can be committed
together.

0004-0006 are the patches that were previously 0002-0004.

The new version also contains a change to what was previously 0001 patch
(attached 0002) -- eq_classes is now copied right when the child subroot
is created, that is, in create_inherited_target_child_root(), no longer in
the loop in set_inherit_target_rel_sizes().

Thanks,
Amit

Attachments: download all (6 files, .tar.gz)

v21-0001-Reduce-code-duplication-in-set_append_rel_size.patch (text/plain, 2.9K) download From 8f792a8492d5579c238cea8ba24bbebe6ace4d46 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 8 Feb 2019 15:06:25 +0900 Subject: [PATCH v21 1/6] Reduce code-duplication in set_append_rel_size The code to mark a child rel dummy is repeated for 3 cases: * child is pruned, * contradictory quals found in apply_child_quals, * child excluded due to constraints. This combines the three cases in one if statement. --- src/backend/optimizer/path/allpaths.c | 39 +++++++++++------------------------ 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 55b871c02c..130efd6023 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -1015,6 +1015,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, RelOptInfo *childrel; ListCell *parentvars; ListCell *childvars; + bool childpruned; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1030,36 +1031,20 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - + childpruned = (did_pruning && + !bms_is_member(appinfo->child_relid, live_children)); /* - * 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 - * baserestrictinfo quals. + * Unless the child is pruned, 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 baserestrictinfo quals. */ - if (!apply_child_basequals(root, rel, childrel, childRTE, appinfo)) + if (childpruned || + !apply_child_basequals(root, rel, childrel, childRTE, appinfo) || + relation_excluded_by_constraints(root, childrel, childRTE)) { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* - * This child need not be scanned, so we can omit it from the - * appendrel. - */ + /* This partition needn't be scanned; skip it. */ set_dummy_rel_pathlist(childrel); continue; } -- 2.11.0
v21-0002-Overhaul-inheritance-update-delete-planning.patch (text/plain, 64.5K) download From 6349799f1aaf5cd440bf16114ebd243a796a85f9 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 8 Feb 2019 16:16:38 +0900 Subject: [PATCH v21 2/6] Overhaul inheritance update/delete planning With the current method, inheritance_planner, which handles the planning for update/delete commands targeting inheritance trees, repeatedly calls query_planner for each child table. That's inefficient for two reasons: 1. partprune.c cannot be used to perform partition pruning, because it can only be invoked from query_planner. With the current approach, query_planner only sees the individual partitions in the query tree it receives, not the parent partitioned table. This leaves each partition to be pruned using constraint exclusion. (constraint exclusion cannon prune hash partitions which means UPDATE/DELETE don't support hash partition pruning.) 2. Repeated invocation of query_planner results in allocating large amounts of memory, especially if there are many child tables. Also, it involves repeatedly performing the same processing, such as jointree processing. This commit addresses both of the issues by modifying query_planner to handle the inheritance target case directly. Instead of having the caller create separate PlannerInfo's for individual child target relations and pass them to query_planner (actually, via grouping_planner), this teaches query_planner itself to create the child PlannerInfo's by the original parent PlannerInfo. It creates them *after* it has generated enough state in the latter, such as RelOptInfos for various relations, EquivalenceClasses after processing the join tree, and *before* generating the scan and join paths. Scan paths are generated normally, except that Paths of target children are not "appended". Join paths are generated separately for each child target relation, that is, by replacing the parent relation references in the original join tree with the child relation references. Joinrels (RelOptInfos thereof) corresponding to various child target relation are saved in the parent PlannerInfo for further processing. inheritance_planner now calls query_planner just once at the beginning with the original unmodified query, which takes care of generating top-level join rels for the child queries as described above. For each child join rel, it then calls grouping_planner using previously created child PlannerInfo to finish up the paths such that they produce query's top-level target list expanded according to a given child relation's descriptor. grouping_planner()'s interface is modified so that we can pass it what's called a 'planned_rel', a RelOptInfo that already contains the necessary paths needed to produce its output. This removes some existing code in inheritance_planner that dealt with any subquery RTEs in the query. The rationale of that code was that the subquery RTEs may change during each iteration of planning (that is, for different children), so different iterations better use different copies of those RTEs. That was handled by making fresh copies of those RTEs for each iteration of planning which were appended to the range table, accompanied by modifying all expressions and auxiliary structures that referenced the original subquery RTEs to instead reference the copies (that is, change the varnos). This copying meant we would end up adding S * N new entries to the original range table by the time we got to the last unpruned child, where S is the number of subquery RTEs in the original query and N the number of unpruned children. Since with the new code we perform planning just once, I think we don't need this special handling. Actually, there is a regression test output change due to no longer having copies of subquery RTEs (see the diff of partition_join.out file.) Also we no longer need the hack in relation_excluded_by_constraints() involving setting PlannerInfo.inhTargetKind to enable constraint exclusion for target child relation, which is no longer needed. Constraint exclusion now runs during query_planner step described above. Regression test output change in partition_join.out is due to the fact that we no longer create duplicates of subquery RTEs in child range tables. --- doc/src/sgml/ddl.sgml | 17 +- src/backend/optimizer/path/allpaths.c | 453 +++++++++++++++++++++++++-- src/backend/optimizer/path/equivclass.c | 5 +- src/backend/optimizer/path/joinrels.c | 10 +- src/backend/optimizer/plan/planmain.c | 42 ++- src/backend/optimizer/plan/planner.c | 426 +++++++------------------ src/backend/optimizer/util/appendinfo.c | 160 +++++++++- src/backend/optimizer/util/inherit.c | 117 +++++++ src/include/nodes/pathnodes.h | 37 ++- src/include/optimizer/inherit.h | 1 + src/include/optimizer/paths.h | 3 + src/test/regress/expected/partition_join.out | 4 +- 12 files changed, 900 insertions(+), 375 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index ef713a5a1c..f4331bcf65 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -4439,24 +4439,11 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <note> <para> - Currently, pruning of partitions during the planning of an - <command>UPDATE</command> or <command>DELETE</command> command is - implemented using the constraint exclusion method (however, it is - controlled by the <literal>enable_partition_pruning</literal> rather than - <literal>constraint_exclusion</literal>) &mdash; see the following section - for details and caveats that apply. - </para> - - <para> Execution-time partition pruning currently only occurs for the <literal>Append</literal> and <literal>MergeAppend</literal> node types. It is not yet implemented for the <literal>ModifyTable</literal> node - type. - </para> - - <para> - Both of these behaviors are likely to be changed in a future release - of <productname>PostgreSQL</productname>. + type, but that is likely to be changed in a future release of + <productname>PostgreSQL</productname>. </para> </note> </sect2> diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 130efd6023..4bbd9aa84d 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -39,6 +39,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" @@ -92,8 +93,12 @@ static void set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); +static void set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte); static void set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); +static void set_inherit_target_rel_pathlists(PlannerInfo *root, + RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, @@ -122,6 +127,8 @@ static void set_result_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); +static RelOptInfo *inheritance_make_rel_from_joinlist(PlannerInfo *root, + List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, pushdown_safety_info *safetyInfo); static bool recurse_pushdown_safe(Node *setOp, Query *topquery, @@ -155,27 +162,6 @@ make_one_rel(PlannerInfo *root, List *joinlist) Index rti; double total_pages; - /* - * Construct the all_baserels Relids set. - */ - root->all_baserels = NULL; - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - - /* there may be empty slots corresponding to non-baserel RTEs */ - if (brel == NULL) - continue; - - Assert(brel->relid == rti); /* sanity check on array */ - - /* ignore RTEs that are "other rels" */ - if (brel->reloptkind != RELOPT_BASEREL) - continue; - - root->all_baserels = bms_add_member(root->all_baserels, brel->relid); - } - /* Mark base rels as to whether we care about fast-start plans */ set_base_rel_consider_startup(root); @@ -223,13 +209,35 @@ make_one_rel(PlannerInfo *root, List *joinlist) /* * Generate access paths for the entire join tree. + * + * For UPDATE/DELETE on an inheritance parent, join paths should be + * generated for each child result rel separately. */ - rel = make_rel_from_joinlist(root, joinlist); + if (root->parse->resultRelation > 0 && + root->simple_rte_array[root->parse->resultRelation]->inh) + { + /* + * RelOptInfo corresponding to the query's original target relation + * is returned. Join paths (if any) are attached to child joinrels, + * not this rel. Also, this rel's sole path (ModifyTable) will be set + * by inheritance_planner later, so we can't check its paths yet. + */ + rel = inheritance_make_rel_from_joinlist(root, joinlist); + } + else + { + rel = make_rel_from_joinlist(root, joinlist); - /* - * The result should join all and only the query's base rels. - */ - Assert(bms_equal(rel->relids, root->all_baserels)); + /* + * The result should join all and only the query's base rels. + */ + Assert(bms_equal(rel->relids, root->all_baserels)); + + /* Check that we got at least one usable path */ + if (!rel || !rel->cheapest_total_path || + rel->cheapest_total_path->param_info != NULL) + elog(ERROR, "failed to construct the join relation"); + } return rel; } @@ -379,8 +387,15 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_size(root, rel, rti, rte); + /* + * If it's a target relation, set the sizes of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (rti == root->parse->resultRelation) + set_inherit_target_rel_sizes(root, rel, rti, rte); + else + set_append_rel_size(root, rel, rti, rte); } else { @@ -469,14 +484,26 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { + bool inherited_update = false; + if (IS_DUMMY_REL(rel)) { /* We already proved the relation empty, so nothing more to do */ } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_pathlist(root, rel, rti, rte); + /* + * If it's a target relation, set the pathlists of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (rti == root->parse->resultRelation) + { + inherited_update = true; + set_inherit_target_rel_pathlists(root, rel, rti, rte); + } + else + set_append_rel_pathlist(root, rel, rti, rte); } else { @@ -554,8 +581,12 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, if (set_rel_pathlist_hook) (*set_rel_pathlist_hook) (root, rel, rti, rte); - /* Now find the cheapest of the paths for this rel */ - set_cheapest(rel); + /* + * Now find the cheapest of the paths for this rel, unless it's an + * inheritance parent and this is an update/delete operation. + */ + if (!inherited_update) + set_cheapest(rel); #ifdef OPTIMIZER_DEBUG debug_print_rel(root, rel); @@ -921,6 +952,212 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) } /* + * set_inherit_target_rel_sizes + * Set size estimates for the child target relations + * + * The passed-in rel represents the target relation of the query that is + * known to have inheritance children. This is very much like + * set_append_rel_size, except it doesn't set the size estimates for the + * passed-in rel itself, because we don't need to "append" the children + * in this case. + */ +static void +set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + bool has_live_children = false; + ListCell *l; + Relids live_children = NULL; + bool did_pruning = false; + + /* Guard against stack overflow due to overly deep inheritance tree. */ + check_stack_depth(); + + Assert(IS_SIMPLE_REL(rel)); + + /* + * If the partitioned relation has any baserestrictinfo quals then we + * attempt to use these quals to prune away partitions that cannot + * possibly contain any tuples matching these quals. In this case we'll + * store the relids of all partitions which could possibly contain a + * matching tuple, and skip anything else in the loop below. + */ + if (enable_partition_pruning && + rte->relkind == RELKIND_PARTITIONED_TABLE && + rel->baserestrictinfo != NIL) + { + live_children = prune_append_rel_partitions(rel); + did_pruning = true; + } + + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + PlannerInfo *subroot; + ListCell *lc; + List *translated_exprs, + *child_target_exprs; + bool childpruned; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + + /* + * The child rel's RelOptInfo was already created during + * add_base_rels_to_query. + */ + childrel = find_base_rel(root, childRTindex); + Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); + + childpruned = (did_pruning && + !bms_is_member(appinfo->child_relid, live_children)); + + /* + * Unless the child is pruned, 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 baserestrictinfo quals. + */ + if (childpruned || + !apply_child_basequals(root, rel, childrel, childRTE, appinfo) || + relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* This partition needn't be scanned; skip it. */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Add missing Vars to child's reltarget. + * + * create_inherit_target_child_root() would've added only those that + * are needed to be present in the top-level tlist (or ones that + * preprocess_targetlist thinks are needed to be in the tlist.) We + * may need other attributes such as those contained in WHERE clauses, + * which are already computed for the parent during + * deconstruct_jointree processing of the original query (child's + * query never goes through deconstruct_jointree.) + */ + translated_exprs = (List *) + adjust_appendrel_attrs(root, + (Node *) rel->reltarget->exprs, + 1, &appinfo); + child_target_exprs = childrel->reltarget->exprs; + foreach(lc, translated_exprs) + { + Expr *expr = lfirst(lc); + + if (!list_member(child_target_exprs, expr)) + child_target_exprs = lappend(child_target_exprs, expr); + } + + childrel->has_eclass_joins = rel->has_eclass_joins; + + subroot = root->inh_target_child_roots[childRTindex]; + + /* Translate join quals. */ + childrel->joininfo = (List *) + adjust_appendrel_attrs(subroot, + (Node *) rel->joininfo, + 1, &appinfo); + + /* + * Compute the child's size using possibly modified subroot. + */ + set_rel_size(subroot, childrel, childRTindex, childRTE); + + /* If the child itself is partitioned it may turn into a dummy rel. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* We have at least one live child. */ + has_live_children = true; + + Assert(childrel->rows > 0); + } + + if (has_live_children) + { + /* + * Set a non-zero value here to cope with the caller's requirement + * that non-dummy relations are actually not empty. We don't try to + * be accurate here, because we're not going to create a path that + * combines the children outputs. + */ + rel->rows = 1; + } + else + { + /* + * All children were excluded by constraints, so mark the relation + * as dummy. We must do this in this phase so that the rel's + * dummy-ness is visible when we generate paths for other rels. + */ + set_dummy_rel_pathlist(rel); + } +} + +/* + * set_inherit_target_rel_pathlists + * Build access paths for the child target relations + * + * Similar to set_append_rel_pathlist, except that we build paths of the + * children, but don't build an Append path. + */ +static void +set_inherit_target_rel_pathlists(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + ListCell *l; + + /* Generate access paths for each of the children of passed-in rel */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + PlannerInfo *subroot; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + /* Re-locate the child RTE and RelOptInfo */ + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + childrel = root->simple_rel_array[childRTindex]; + subroot = root->inh_target_child_roots[childRTindex]; + /* Transfer the value from main root to subroot. */ + subroot->total_table_pages = root->total_table_pages; + + /* + * If set_append_rel_size() decided the parent appendrel was + * parallel-unsafe at some point after visiting this child rel, we + * need to propagate the unsafety marking down to the child, so that + * we don't generate useless partial paths for it. + */ + if (!rel->consider_parallel) + childrel->consider_parallel = false; + + /* + * Compute the child's access paths. + */ + set_rel_pathlist(subroot, childrel, childRTindex, childRTE); + } +} + +/* * set_append_rel_size * Set size estimates for a simple "append relation" * @@ -2548,6 +2785,158 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows) } /* + * inheritance_make_rel_from_joinlist + * Perform join planning for all non-dummy leaf inheritance children + * in their role as an UPDATE/DELETE query's target relation + * + * If a child relation is a partitioned table, its children are processed in + * turn by recursively calling this function. + */ +static RelOptInfo * +inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) +{ + Index resultRelation = root->parse->resultRelation; + RelOptInfo *resultrel; + ListCell *lc; +#ifdef USE_ASSERT_CHECKING + Relids all_baserels; +#endif + + /* For UPDATE/DELETE queries, the top parent can only ever be a table. */ + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + Assert(planner_rt_fetch(resultRelation, root)->rtekind == RTE_RELATION); + resultrel = find_base_rel(root, resultRelation); + + /* Nothing to do. */ + if (IS_DUMMY_REL(resultrel)) + return resultrel; + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + PlannerInfo *subroot; + RelOptInfo *childrel; + RelOptInfo *childjoinrel; + List *translated_joinlist; + + if (appinfo->parent_relid != resultRelation) + continue; + + childrel = find_base_rel(root, appinfo->child_relid); + + /* Ignore excluded/pruned children. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* Add this child. */ + root->inh_target_child_rels = lappend_int(root->inh_target_child_rels, + appinfo->child_relid); + + /* Perform join planning with child subroot. */ + subroot = root->inh_target_child_roots[appinfo->child_relid]; + Assert(subroot->parse->resultRelation > 0); + + /* + * Modify joinlist such that relations joined to the top parent rel + * appear to be joined to the child rel instead. Do the same for + * any SpecialJoinInfo structs. + */ + translated_joinlist = (List *) + adjust_appendrel_attrs(subroot, + (Node *) joinlist, + 1, &appinfo); + subroot->join_info_list = (List *) + adjust_appendrel_attrs(subroot, + (Node *) root->join_info_list, + 1, &appinfo); + + /* + * Sub-partitioned tables have to be processed recursively using the + * translated subroot as the parent, because AppendRelInfos link + * sub-partitions to their immediate parents, not the root partitioned + * table. + */ + if (childrel->part_scheme != NULL) + { + /* + * inheritance_make_rel_from_joinlist() return the target relation + * RelOptInfo. + */ + childrel = + inheritance_make_rel_from_joinlist(subroot, + translated_joinlist); + + /* + * Add this child relation as a placeholder in the parent root's + * inh_target_child_path_rels so that inheritance_planner sees + * same number of entries in it as inh_target_child_rels. + */ + root->inh_target_child_path_rels = + lappend(root->inh_target_child_path_rels, childrel); + + /* + * Also propagate this child's own children into the parent's + * list. + */ + if (subroot->inh_target_child_rels != NIL) + { + root->inh_target_child_rels = + list_concat(root->inh_target_child_rels, + subroot->inh_target_child_rels); + root->inh_target_child_path_rels = + list_concat(root->inh_target_child_path_rels, + subroot->inh_target_child_path_rels); + } + continue; + } + + /* + * Since we added the child rel directly into the join tree, we must + * modify it to be a "base" rel instead of an "other" rel, which the + * join planning code expects the relations being joined to be. + */ + childrel->reloptkind = RELOPT_BASEREL; + + Assert(subroot->join_rel_list == NIL); + Assert(subroot->join_rel_hash == NULL); + + /* Perform join planning and save the resulting RelOptInfo. */ + childjoinrel = make_rel_from_joinlist(subroot, translated_joinlist); + + /* Check that we got at least one usable path */ + if (!childjoinrel || !childjoinrel->cheapest_total_path || + childjoinrel->cheapest_total_path->param_info != NULL) + elog(ERROR, "failed to construct the child join relation"); + + /* + * Remember the paths of child target rel. inheritance_planner will + * perform the remaining steps of planning for each child relation + * separately. Specifically, it will call grouping_planner on every + * RelOptInfo contained in the inh_target_child_rels list, each of + * which represents the source of tuples to be modified for a given + * target child rel. + */ + root->inh_target_child_path_rels = + lappend(root->inh_target_child_path_rels, childjoinrel); +#ifdef USE_ASSERT_CHECKING + /* + * The following implements essentially the same Assert as in + * make_one_rel, our caller. + */ + all_baserels = bms_copy(root->all_baserels); + all_baserels = bms_del_member(all_baserels, + root->parse->resultRelation); + all_baserels = bms_add_member(all_baserels, + subroot->parse->resultRelation); + Assert(bms_equal(childjoinrel->relids, all_baserels)); +#endif + } + + return resultrel; +} + +/* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. * diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 3454f12912..332dc34276 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -32,9 +32,6 @@ #include "utils/lsyscache.h" -static EquivalenceMember *add_eq_member(EquivalenceClass *ec, - Expr *expr, Relids relids, Relids nullable_relids, - bool is_child, Oid datatype); static void generate_base_implied_equalities_const(PlannerInfo *root, EquivalenceClass *ec); static void generate_base_implied_equalities_no_const(PlannerInfo *root, @@ -540,7 +537,7 @@ canonicalize_ec_expression(Expr *expr, Oid req_type, Oid req_collation) /* * add_eq_member - build a new EquivalenceMember and add it to an EC */ -static EquivalenceMember * +EquivalenceMember * add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids, Relids nullable_relids, bool is_child, Oid datatype) { diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index dfbbfdac6d..a02321486f 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1462,8 +1462,14 @@ update_child_rel_info(PlannerInfo *root, (Node *) rel->reltarget->exprs, 1, &appinfo); - /* Make child entries in the EquivalenceClass as well */ - if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) + /* + * Make child entries in the EquivalenceClass as well. If the childrel + * appears to be a dummy one (one built by build_dummy_partition_rel()), + * no need to make any new entries, because anything that'd need those + * can instead use the parent's (rel). + */ + if (childrel->relid != rel->relid && + (rel->has_eclass_joins || has_useful_pathkeys(root, rel))) add_child_rel_equivalences(root, appinfo, rel, childrel); childrel->has_eclass_joins = rel->has_eclass_joins; } diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 3cedd01c98..0b1c8a2529 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -20,6 +20,7 @@ */ #include "postgres.h" +#include "nodes/pathnodes.h" #include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/inherit.h" @@ -29,6 +30,7 @@ #include "optimizer/paths.h" #include "optimizer/placeholder.h" #include "optimizer/planmain.h" +#include "optimizer/prep.h" /* @@ -60,6 +62,7 @@ query_planner(PlannerInfo *root, List *tlist, Query *parse = root->parse; List *joinlist; RelOptInfo *final_rel; + Index rti; /* * Init planner lists to empty. @@ -260,14 +263,43 @@ query_planner(PlannerInfo *root, List *tlist, extract_restriction_or_clauses(root); /* + * Construct the all_baserels Relids set. + */ + root->all_baserels = NULL; + for (rti = 1; rti < root->simple_rel_array_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + Assert(brel->relid == rti); /* sanity check on array */ + + /* ignore RTEs that are "other rels" */ + if (brel->reloptkind != RELOPT_BASEREL) + continue; + + root->all_baserels = bms_add_member(root->all_baserels, brel->relid); + } + + /* + * Add child subroots needed to use during planning for individual child + * targets + */ + if (parse->resultRelation && + root->simple_rte_array[parse->resultRelation]->inh) + { + root->inh_target_child_roots = (PlannerInfo **) + palloc0(root->simple_rel_array_size * + sizeof(PlannerInfo *)); + add_inherited_target_child_roots(root); + } + + /* * Ready to do the primary planning. */ final_rel = make_one_rel(root, joinlist); - /* Check that we got at least one usable path */ - if (!final_rel || !final_rel->cheapest_total_path || - final_rel->cheapest_total_path->param_info != NULL) - elog(ERROR, "failed to construct the join relation"); - return final_rel; } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index b2239728cf..33fbfa5f37 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -131,7 +131,7 @@ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static void inheritance_planner(PlannerInfo *root); static void grouping_planner(PlannerInfo *root, bool inheritance_update, - double tuple_fraction); + RelOptInfo *planned_rel, double tuple_fraction); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, int *tleref_to_colnum_map); @@ -997,7 +997,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh) inheritance_planner(root); else - grouping_planner(root, false, tuple_fraction); + grouping_planner(root, false, NULL, tuple_fraction); /* * Capture the set of outer-level param IDs we have access to, for use in @@ -1173,13 +1173,39 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr) * inheritance set. * * We have to handle this case differently from cases where a source relation - * is an inheritance set. Source inheritance is expanded at the bottom of the - * plan tree (see allpaths.c), but target inheritance has to be expanded at - * the top. The reason is that for UPDATE, each target relation needs a - * different targetlist matching its own column set. Fortunately, + * is an inheritance set, where outputs of all the children are combined and + * combined output consumed using the source relation's (parent's) column set. + * For UPDATE, each target relation, where the query's output will go, needs + * a different targetlist matching its own column set. So, we will need to + * translate the query such that it produces the desired output required by + * each of the child target relations and plan each separately. Fortunately, * the UPDATE/DELETE target can never be the nullable side of an outer join, * so it's OK to generate the plan this way. * + * Planning steps that need to be performed separately for each child and + * any preliminary processing needed for that are handled by the following + * sub-routines of query_planner: + * + * add_inherited_target_child_roots - this creates copies of PlannerInfo for + * each child after query_planner has finished processing the join tree, with + * translated copies of parsetree, eq_classes, etc. + * + * set_inherit_target_rel_sizes - this sets size estimates for child + * relations + * + * set_inherit_target_rel_pathlists - this creates Paths for scanning + * individual child relations + * + * inherit_make_rel_from_joinlist - this translates the jointree, replacing + * the target relation in the original jointree (the root parent) by + * individual child target relations and performs join planning on the + * resulting join tree, saving the RelOptInfos of resulting join relations + * into the top-level PlannerInfo + * + * Finally, here we apply grouping_planner to each child join relation + * produced by set_inherit_target_rel_pathlists to so that its path produces + * the desired targetlist. + * * Returns nothing; the useful output is in the Paths we attach to * the (UPPERREL_FINAL, NULL) upperrel stored in *root. * @@ -1191,14 +1217,8 @@ inheritance_planner(PlannerInfo *root) { Query *parse = root->parse; int top_parentRTindex = parse->resultRelation; - Bitmapset *subqueryRTindexes; - Bitmapset *modifiableARIindexes; int nominalRelation = -1; Index rootRelation = 0; - List *final_rtable = NIL; - int save_rel_array_size = 0; - RelOptInfo **save_rel_array = NULL; - AppendRelInfo **save_append_rel_array = NULL; List *subpaths = NIL; List *subroots = NIL; List *resultRelations = NIL; @@ -1206,70 +1226,49 @@ inheritance_planner(PlannerInfo *root) List *returningLists = NIL; List *rowMarks; RelOptInfo *final_rel; - ListCell *lc; - Index rti; + ListCell *lc1, + *lc2; RangeTblEntry *parent_rte; - PlannerInfo *parent_root; - Query *parent_parse; - Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); - PlannerInfo **parent_roots = NULL; + List *tlist; + standard_qp_extra qp_extra; + RelOptInfo *planned_rel; + /* Inheritance is never used for insert. */ Assert(parse->commandType != CMD_INSERT); /* - * We generate a modified instance of the original Query for each target - * relation, plan that, and put all the plans into a list that will be - * controlled by a single ModifyTable node. All the instances share the - * same rangetable, but each instance must have its own set of subquery - * RTEs within the finished rangetable because (1) they are likely to get - * scribbled on during planning, and (2) it's not inconceivable that - * subqueries could get planned differently in different cases. We need - * not create duplicate copies of other RTE kinds, in particular not the - * target relations, because they don't have either of those issues. Not - * having to duplicate the target relations is important because doing so - * (1) would result in a rangetable of length O(N^2) for N targets, with - * at least O(N^3) work expended here; and (2) would greatly complicate - * management of the rowMarks list. + * Let query_planner generate the access paths for the query for each + * target child relation. * - * To begin with, generate a bitmapset of the relids of the subquery RTEs. + * First, save the unexpanded version of the query's targetlist. + * create_inherit_target_child_root will use it as base when expanding + * it for a given child relation as the query's target relation instead + * of the parent. */ - subqueryRTindexes = NULL; - rti = 1; - foreach(lc, parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); + root->unexpanded_tlist = list_copy(root->parse->targetList); - if (rte->rtekind == RTE_SUBQUERY) - subqueryRTindexes = bms_add_member(subqueryRTindexes, rti); - rti++; - } + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root); + root->processed_tlist = tlist; + qp_extra.tlist = tlist; + qp_extra.activeWindows = NIL; + qp_extra.groupClause = NIL; + planned_rel = query_planner(root, tlist, standard_qp_callback, &qp_extra); /* - * Next, we want to identify which AppendRelInfo items contain references - * to any of the aforesaid subquery RTEs. These items will need to be - * copied and modified to adjust their subquery references; whereas the - * other ones need not be touched. It's worth being tense over this - * because we can usually avoid processing most of the AppendRelInfo - * items, thereby saving O(N^2) space and time when the target is a large - * inheritance tree. We can identify AppendRelInfo items by their - * child_relid, since that should be unique within the list. + * If it turned out during query planning that all the children are dummy + * (pruned or excluded by constraints), no need to do the steps below. + * Let grouping_planner finish up the final path. */ - modifiableARIindexes = NULL; - if (subqueryRTindexes != NULL) + if (IS_DUMMY_REL(planned_rel)) { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - - if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) || - bms_is_member(appinfo->child_relid, subqueryRTindexes) || - bms_overlap(pull_varnos((Node *) appinfo->translated_vars), - subqueryRTindexes)) - modifiableARIindexes = bms_add_member(modifiableARIindexes, - appinfo->child_relid); - } + grouping_planner(root, false, planned_rel, 0.0); + return; } + Assert(planned_rel->relid == top_parentRTindex); + Assert(planned_rel->reloptkind == RELOPT_BASEREL); + /* * If the parent RTE is a partitioned table, we should use that as the * nominal target relation, because the RTEs added for partitioned tables @@ -1277,7 +1276,7 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); + parent_rte = planner_rt_fetch(top_parentRTindex, root); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1285,66 +1284,29 @@ inheritance_planner(PlannerInfo *root) } /* - * The PlannerInfo for each child is obtained by translating the relevant - * members of the PlannerInfo for its immediate parent, which we find - * using the parent_relid in its AppendRelInfo. We save the PlannerInfo - * for each parent in an array indexed by relid for fast retrieval. Since - * the maximum number of parents is limited by the number of RTEs in the - * query, we use that number to allocate the array. An extra entry is - * needed since relids start from 1. + * Get on with finalizing the path for each child target relation by + * calling grouping_planner on its joinrel. */ - parent_roots = (PlannerInfo **) palloc0((list_length(parse->rtable) + 1) * - sizeof(PlannerInfo *)); - parent_roots[top_parentRTindex] = root; - - /* - * And now we can get on with generating a plan for each child table. - */ - foreach(lc, root->append_rel_list) + forboth(lc1, root->inh_target_child_rels, + lc2, root->inh_target_child_path_rels) { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - PlannerInfo *subroot; + Index childRTindex = lfirst_int(lc1); + RelOptInfo *childjoinrel = lfirst(lc2); + PlannerInfo *subroot = root->inh_target_child_roots[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; RangeTblEntry *child_rte; RelOptInfo *sub_final_rel; Path *subpath; - /* append_rel_list contains all append rels; ignore others */ - if (!bms_is_member(appinfo->parent_relid, parent_relids)) - continue; - - /* - * expand_inherited_rtentry() always processes a parent before any of - * that parent's children, so the parent_root for this relation should - * already be available. - */ - parent_root = parent_roots[appinfo->parent_relid]; - Assert(parent_root != NULL); - parent_parse = parent_root->parse; - - /* - * We need a working copy of the PlannerInfo so that we can control - * propagation of information back to the main copy. - */ - subroot = makeNode(PlannerInfo); - memcpy(subroot, parent_root, sizeof(PlannerInfo)); - - /* - * Generate modified query with this rel as target. We first apply - * adjust_appendrel_attrs, which copies the Query and changes - * references to the parent RTE to refer to the current child RTE, - * then fool around with subquery RTEs. - */ - subroot->parse = (Query *) - adjust_appendrel_attrs(parent_root, - (Node *) parent_parse, - 1, &appinfo); + Assert(subroot != NULL); + Assert(subroot->parse->resultRelation == childRTindex); /* * If there are securityQuals attached to the parent, move them to the * child rel (they've already been transformed properly for that). */ - parent_rte = rt_fetch(appinfo->parent_relid, subroot->parse->rtable); - child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable); + parent_rte = planner_rt_fetch(appinfo->parent_relid, subroot); + child_rte = planner_rt_fetch(appinfo->child_relid, subroot); child_rte->securityQuals = parent_rte->securityQuals; parent_rte->securityQuals = NIL; @@ -1356,24 +1318,12 @@ inheritance_planner(PlannerInfo *root) (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED; /* - * If this child is further partitioned, remember it as a parent. - * Since a partitioned table does not have any data, we don't need to - * create a plan for it, and we can stop processing it here. We do, - * however, need to remember its modified PlannerInfo for use when - * processing its children, since we'll update their varnos based on - * the delta from immediate parent to child, not from top to child. - * - * Note: a very non-obvious point is that we have not yet added - * duplicate subquery RTEs to the subroot's rtable. We mustn't, - * because then its children would have two sets of duplicates, - * confusing matters. + * Ignore a partitioned child. Instead, the paths of its children will + * be added to subpaths. */ - if (child_rte->inh) + if (childjoinrel->part_scheme) { - Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE); - parent_relids = bms_add_member(parent_relids, appinfo->child_relid); - parent_roots[appinfo->child_relid] = subroot; - + Assert(child_rte->inh); continue; } @@ -1401,111 +1351,8 @@ inheritance_planner(PlannerInfo *root) if (nominalRelation < 0) nominalRelation = appinfo->child_relid; - /* - * The rowMarks list might contain references to subquery RTEs, so - * make a copy that we can apply ChangeVarNodes to. (Fortunately, the - * executor doesn't need to see the modified copies --- we can just - * pass it the original rowMarks list.) - */ - subroot->rowMarks = copyObject(parent_root->rowMarks); - - /* - * The append_rel_list likewise might contain references to subquery - * RTEs (if any subqueries were flattenable UNION ALLs). So prepare - * to apply ChangeVarNodes to that, too. As explained above, we only - * want to copy items that actually contain such references; the rest - * can just get linked into the subroot's append_rel_list. - * - * If we know there are no such references, we can just use the outer - * append_rel_list unmodified. - */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - subroot->append_rel_list = NIL; - foreach(lc2, parent_root->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, modifiableARIindexes)) - appinfo2 = copyObject(appinfo2); - - subroot->append_rel_list = lappend(subroot->append_rel_list, - appinfo2); - } - } - - /* - * Add placeholders to the child Query's rangetable list to fill the - * RT indexes already reserved for subqueries in previous children. - * These won't be referenced, so there's no need to make them very - * valid-looking. - */ - while (list_length(subroot->parse->rtable) < list_length(final_rtable)) - subroot->parse->rtable = lappend(subroot->parse->rtable, - makeNode(RangeTblEntry)); - - /* - * If this isn't the first child Query, generate duplicates of all - * subquery RTEs, and adjust Var numbering to reference the - * duplicates. To simplify the loop logic, we scan the original rtable - * not the copy just made by adjust_appendrel_attrs; that should be OK - * since subquery RTEs couldn't contain any references to the target - * rel. - */ - if (final_rtable != NIL && subqueryRTindexes != NULL) - { - ListCell *lr; - - rti = 1; - foreach(lr, parent_parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lr); - - if (bms_is_member(rti, subqueryRTindexes)) - { - Index newrti; - - /* - * The RTE can't contain any references to its own RT - * index, except in its securityQuals, so we can save a - * few cycles by applying ChangeVarNodes to the rest of - * the rangetable before we append the RTE to it. - */ - newrti = list_length(subroot->parse->rtable) + 1; - ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); - ChangeVarNodes((Node *) subroot->rowMarks, rti, newrti, 0); - /* Skip processing unchanging parts of append_rel_list */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - foreach(lc2, subroot->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, - modifiableARIindexes)) - ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); - } - } - rte = copyObject(rte); - ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); - subroot->parse->rtable = lappend(subroot->parse->rtable, - rte); - } - rti++; - } - } - - /* There shouldn't be any OJ info to translate, as yet */ - Assert(subroot->join_info_list == NIL); - /* and we haven't created PlaceHolderInfos, either */ - Assert(subroot->placeholder_list == NIL); - - /* Generate Path(s) for accessing this result relation */ - grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ ); + /* Apply the top-level targetlist to childjoinrel's path. */ + grouping_planner(subroot, true, childjoinrel, 0.0); /* * Select cheapest path in case there's more than one. We always run @@ -1517,45 +1364,10 @@ inheritance_planner(PlannerInfo *root) subpath = sub_final_rel->cheapest_total_path; /* - * If this child rel was excluded by constraint exclusion, exclude it - * from the result plan. + * child rel cannot be empty, or inheritance_make_rel_from_joinlist + * wouldn't have put it in the list to begin with. */ - if (IS_DUMMY_PATH(subpath)) - continue; - - /* - * If this is the first non-excluded child, its post-planning rtable - * becomes the initial contents of final_rtable; otherwise, append - * just its modified subquery RTEs to final_rtable. - */ - if (final_rtable == NIL) - final_rtable = subroot->parse->rtable; - else - final_rtable = list_concat(final_rtable, - list_copy_tail(subroot->parse->rtable, - list_length(final_rtable))); - - /* - * We need to collect all the RelOptInfos from all child plans into - * the main PlannerInfo, since setrefs.c will need them. We use the - * last child's simple_rel_array (previous ones are too short), so we - * have to propagate forward the RelOptInfos that were already built - * in previous children. - */ - Assert(subroot->simple_rel_array_size >= save_rel_array_size); - for (rti = 1; rti < save_rel_array_size; rti++) - { - RelOptInfo *brel = save_rel_array[rti]; - - if (brel) - subroot->simple_rel_array[rti] = brel; - } - save_rel_array_size = subroot->simple_rel_array_size; - save_rel_array = subroot->simple_rel_array; - save_append_rel_array = subroot->append_rel_array; - - /* Make sure any initplans from this rel get into the outer list */ - root->init_plans = subroot->init_plans; + Assert(!IS_DUMMY_PATH(subpath)); /* Build list of sub-paths */ subpaths = lappend(subpaths, subpath); @@ -1587,36 +1399,6 @@ inheritance_planner(PlannerInfo *root) */ /* - * If we managed to exclude every child rel, return a dummy plan; it - * doesn't even need a ModifyTable node. - */ - if (subpaths == NIL) - { - set_dummy_rel_pathlist(final_rel); - return; - } - - /* - * Put back the final adjusted rtable into the master copy of the Query. - * (We mustn't do this if we found no non-excluded children.) - */ - parse->rtable = final_rtable; - root->simple_rel_array_size = save_rel_array_size; - root->simple_rel_array = save_rel_array; - root->append_rel_array = save_append_rel_array; - - /* Must reconstruct master's simple_rte_array, too */ - root->simple_rte_array = (RangeTblEntry **) - palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *)); - rti = 1; - foreach(lc, final_rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - root->simple_rte_array[rti++] = rte; - } - - /* * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will * have dealt with fetching non-locked marked rows, else we need to have * ModifyTable do that. @@ -1656,6 +1438,12 @@ inheritance_planner(PlannerInfo *root) * (inheritance_planner will create a single ModifyTable node covering all the * target tables.) * + * If non-NULL, planned_rel is a RelOptInfo containing paths for the query's + * top-level joinrel, which the caller produced by itself. In that case, this + * function only needs to adjust the targetlist of its cheapest_total_path. + * The only caller that may pass such a RelOptInfo currently is + * inheritance_planner. + * * tuple_fraction is the fraction of tuples we expect will be retrieved. * tuple_fraction is interpreted as follows: * 0: expect all tuples to be retrieved (normal case) @@ -1674,6 +1462,7 @@ inheritance_planner(PlannerInfo *root) */ static void grouping_planner(PlannerInfo *root, bool inheritance_update, + RelOptInfo *planned_rel, double tuple_fraction) { Query *parse = root->parse; @@ -1686,7 +1475,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, List *final_targets; List *final_targets_contain_srfs; bool final_target_parallel_safe; - RelOptInfo *current_rel; + RelOptInfo *current_rel = planned_rel; RelOptInfo *final_rel; ListCell *lc; @@ -1726,6 +1515,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * special work for recursive unions is the responsibility of * plan_set_operations. */ + Assert(current_rel == NULL); current_rel = plan_set_operations(root); /* @@ -1815,17 +1605,26 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, parse->groupClause = preprocess_groupclause(root, NIL); } - /* Preprocess targetlist */ - tlist = preprocess_targetlist(root); - /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation so - * that we can transfer its decoration (resnames etc) to the topmost - * tlist of the finished Plan. + * Preprocess targetlist, if needed. If the caller has already done + * query planning, root->processed_tlist already contains the desired + * targetlist. */ - root->processed_tlist = tlist; + if (planned_rel == NULL) + { + tlist = preprocess_targetlist(root); + + /* + * We are now done hacking up the query's targetlist. Most of the + * remaining planning work will be done with the PathTarget + * representation of tlists, but save aside the full representation + * so that we can transfer its decoration (resnames etc) to the + * topmost tlist of the finished Plan. + */ + root->processed_tlist = tlist; + } + else + tlist = root->processed_tlist; /* * Collect statistics about aggregates for estimating costs, and mark @@ -1905,8 +1704,9 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * We also generate (in standard_qp_callback) pathkey representations * of the query's sort clause, distinct clause, etc. */ - current_rel = query_planner(root, tlist, - standard_qp_callback, &qp_extra); + if (current_rel == NULL) + current_rel = query_planner(root, tlist, + standard_qp_callback, &qp_extra); /* * Convert the query's result tlist into PathTarget format. diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index ca6622ece9..7447853db9 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -18,6 +18,8 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" #include "parser/parsetree.h" #include "utils/lsyscache.h" #include "utils/rel.h" @@ -394,8 +396,164 @@ adjust_appendrel_attrs_mutator(Node *node, context->appinfos); return (Node *) phv; } + + /* + * This is needed, because inheritance_make_rel_from_joinlist needs to + * translate root->join_info_list executing make_rel_from_joinlist for a + * given child. + */ + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + + if (IsA(node, EquivalenceClass)) + { + EquivalenceClass *ec = (EquivalenceClass *) node; + EquivalenceClass *new_ec = makeNode(EquivalenceClass); + AppendRelInfo *appinfo = NULL; + RelOptInfo *parent_rel, + *child_rel; + ListCell *lc; + + /* + * First memcpy the existing EC to the new EC, which creates shallow + * copies of all the members and then make copies of members that + * we'll change below. + * + * XXX comment in _copyPathKey says it's OK to recycle EC + * pointers, but as long as we do the whole planning for a + * given child using a given root, copying ECs like this + * shouldn't be a problem. Maybe, the following code could + * be in _copyEquivalenceClass()? + */ + memcpy(new_ec, ec, sizeof(EquivalenceClass)); + new_ec->ec_opfamilies = list_copy(ec->ec_opfamilies); + new_ec->ec_sources = list_copy(ec->ec_sources); + new_ec->ec_derives = list_copy(ec->ec_derives); + new_ec->ec_relids = bms_copy(ec->ec_relids); + new_ec->ec_members = list_copy(ec->ec_members); + + /* + * No point in searching if parent rel not mentioned in eclass; but we + * can't tell that for sure if parent rel is itself a child. + */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + if (bms_is_member(appinfos[cnt]->parent_relid, ec->ec_relids)) + { + appinfo = appinfos[cnt]; + break; + } + } + + if (appinfo == NULL) + return (Node *) new_ec; + + parent_rel = find_base_rel(context->root, appinfo->parent_relid); + child_rel = find_base_rel(context->root, appinfo->child_relid); + if (parent_rel->reloptkind != RELOPT_BASEREL) + return (Node *) new_ec; + + /* + * Check if this EC contains an expression referencing the parent + * relation, translate it to child, and store it in place of + * the original parent expression. + */ + foreach(lc, new_ec->ec_members) + { + EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc); + + if (cur_em->em_is_const) + continue; /* ignore consts here */ + + /* Does it reference parent_rel? */ + if (bms_overlap(cur_em->em_relids, parent_rel->relids)) + { + /* Yes, generate transformed child version */ + Expr *new_expr; + Relids new_relids; + Relids new_nullable_relids; + + new_expr = (Expr *) + adjust_appendrel_attrs(context->root, + (Node *) cur_em->em_expr, + 1, &appinfo); + + /* + * Transform em_relids to match. Note we do *not* do + * pull_varnos(child_expr) here, as for example the + * transformation might have substituted a constant, but we + * don't want the child member to be marked as constant. + */ + new_relids = bms_difference(cur_em->em_relids, + parent_rel->relids); + new_relids = bms_add_members(new_relids, child_rel->relids); + + /* + * And likewise for nullable_relids. Note this code assumes + * parent and child relids are singletons. + */ + new_nullable_relids = cur_em->em_nullable_relids; + if (bms_overlap(new_nullable_relids, parent_rel->relids)) + { + new_nullable_relids = bms_difference(new_nullable_relids, + parent_rel->relids); + new_nullable_relids = bms_add_members(new_nullable_relids, + child_rel->relids); + } + + /* + * The new expression simply replaces the old parent one, and + * em_is_child is set to true so that it's recognized as such + * during child planning. + */ + lfirst(lc) = add_eq_member(new_ec, new_expr, + new_relids, new_nullable_relids, + false, cur_em->em_datatype); + + /* + * We have found and replaced the parent expression, so done + * with EC. + */ + break; + } + } + + /* + * Now fix up EC's relids set. It's OK to modify EC like this, + * because caller must have made a copy of the original EC. + * For example, see adjust_inherited_target_child_root. + */ + new_ec->ec_relids = bms_del_members(new_ec->ec_relids, + parent_rel->relids); + new_ec->ec_relids = bms_add_members(new_ec->ec_relids, + child_rel->relids); + return (Node *) new_ec; + } + /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index eaf788e578..323d8c07e2 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -21,11 +21,14 @@ #include "miscadmin.h" #include "optimizer/appendinfo.h" #include "optimizer/inherit.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "utils/rel.h" +static PlannerInfo *create_inherited_target_child_root(PlannerInfo *root, + AppendRelInfo *appinfo); static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti); static void expand_partitioned_rtentry(PlannerInfo *root, @@ -492,3 +495,117 @@ translate_col_privs(const Bitmapset *parent_privs, return child_privs; } + +/* + * add_inherited_target_child_roots + * For each child of the query's result relation, this translates the + * original query to match the child and creates a PlannerInfo containing + * the translated query + * + * Child PlannerInfo reuses most of the parent PlannerInfo's fields unchanged, + * except unexpanded_tlist, processed_tlist, and all_baserels, all of which + * are based on the child relation. + */ +void +add_inherited_target_child_roots(PlannerInfo *root) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; + + Assert(root->inh_target_child_roots != NULL); + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + RangeTblEntry *childRTE; + PlannerInfo *subroot; + + if (appinfo->parent_relid != resultRelation) + continue; + + /* + * Create a PlannerInfo for processing this child target relation + * with. + */ + subroot = create_inherited_target_child_root(root, appinfo); + root->inh_target_child_roots[appinfo->child_relid] = subroot; + + /* + * If the child is a partitioned table, recurse to do this for its + * partitions. + */ + childRTE = root->simple_rte_array[appinfo->child_relid]; + if (childRTE->inh) + add_inherited_target_child_roots(subroot); + } +} + +/* + * create_inherited_target_child_root + * Workhorse of add_inherited_target_child_roots + */ +static PlannerInfo * +create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) +{ + PlannerInfo *subroot; + List *tlist; + + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + + /* + * Translate the original query to replace Vars of the parent table + * by the corresponding Vars of the child table and to make child the main + * target relation of the query. + */ + subroot = makeNode(PlannerInfo); + memcpy(subroot, root, sizeof(PlannerInfo)); + + /* + * Restore the original, unexpanded targetlist, that is, the one before + * preprocess_targetlist was run on the original query. We'll run + * preprocess_targetlist after translating the query and the targetlist, + * so that it is expanded according to child's tuple descriptor. + */ + root->parse->targetList = root->unexpanded_tlist; + subroot->parse = (Query *) adjust_appendrel_attrs(root, + (Node *) root->parse, + 1, &appinfo); + + /* + * Translate ECs. This copies the list of ECs, translating the + * relevant members of individual ECs to replace the references to + * the parent RTE by child RTE, including the EC members. + */ + subroot->eq_classes = (List *) + adjust_appendrel_attrs(root, + (Node *) root->eq_classes, + 1, &appinfo); + + /* + * Save the just translated targetlist as unexpanded_tlist in the child's + * subroot, so that this child's own children can use it. Must use copy + * because subroot->parse->targetList will be modified soon. + */ + subroot->unexpanded_tlist = list_copy(subroot->parse->targetList); + + /* + * Apply planner's expansion of targetlist, such as adding various junk + * column, filling placeholder entries for dropped columns, etc., all of + * which occurs with the child's TupleDesc. + */ + tlist = preprocess_targetlist(subroot); + subroot->processed_tlist = tlist; + + /* Add any newly added Vars to the child RelOptInfo. */ + build_base_rel_tlists(subroot, tlist); + + /* + * Adjust all_baserels to replace the original target relation with the + * child target relation. Copy it before modifying though. + */ + subroot->all_baserels = adjust_child_relids(subroot->all_baserels, + 1, &appinfo); + + return subroot; +} diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index d3c477a542..0b8e90951a 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -325,7 +325,7 @@ struct PlannerInfo Index qual_security_level; /* minimum security_level for quals */ /* Note: qual_security_level is zero if there are no securityQuals */ - InheritanceKind inhTargetKind; /* indicates if the target relation is an + InheritanceKind inhTargetKind; /* indicates if the target relation is an * inheritance child or partition or a * partitioned table */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ @@ -348,9 +348,44 @@ struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* + * The following fields are set during query planning portion of an + * inherited UPDATE/DELETE operation. + */ + + /* + * This stores the original version of the query's targetlist that's + * not modified by the planner. + */ + List *unexpanded_tlist; + + /* + * Array containing simple_rel_array_size elements, indexed by rangetable + * index (entry 0 is wasted like simple_rel_array). Only elements + * corresponding to individual inheritance child target relations are + * non-NULL. Content of each PlannerInfo is mostly same as the parent + * PlannerInfo, except for few fields such as the parse tree which is + * a translated copy of the parent's parse tree, EC list which contain + * child member expressions, etc. + */ + struct PlannerInfo **inh_target_child_roots; + + /* List of RT indexes of child target relations. */ + List *inh_target_child_rels; + + /* + * RelOptInfos corresponding to each child target rel. For leaf children, + * it's the RelOptInfo representing the output of make_rel_from_joinlist() + * called with the parent rel in the original join tree replaced by a + * given leaf child. For non-leaf children, it's the baserel RelOptInfo + * itself, left as a placeholder. + */ + List *inh_target_child_path_rels; }; + /* * In places where it's known that simple_rte_array[] must have been prepared * already, we just index into it to fetch RTEs. In code that might be diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h index d2418f15cf..4f06cdd322 100644 --- a/src/include/optimizer/inherit.h +++ b/src/include/optimizer/inherit.h @@ -18,5 +18,6 @@ extern void expand_inherited_tables(PlannerInfo *root); +extern void add_inherited_target_child_roots(PlannerInfo *root); #endif /* INHERIT_H */ diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 1b02b3b889..749c1f7f3d 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -132,6 +132,9 @@ typedef bool (*ec_matches_callback_type) (PlannerInfo *root, extern bool process_equivalence(PlannerInfo *root, RestrictInfo **p_restrictinfo, bool below_outer_join); +extern EquivalenceMember *add_eq_member(EquivalenceClass *ec, + Expr *expr, Relids relids, Relids nullable_relids, + bool is_child, Oid datatype); extern Expr *canonicalize_ec_expression(Expr *expr, Oid req_type, Oid req_collation); extern void reconsider_outer_join_clauses(PlannerInfo *root); diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index c55de5d476..c9f02c1fb0 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1780,7 +1780,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_1 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_1 -> Nested Loop Semi Join @@ -1788,7 +1788,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_2 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_2 (28 rows) -- 2.11.0
v21-0003-Get-rid-of-some-useless-code.patch (text/plain, 8.5K) download From e5e8fda1e2be42a0f65cd1d3e0342bccabd39ddb Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 8 Feb 2019 16:21:59 +0900 Subject: [PATCH v21 3/6] Get rid of some useless code * plancat.c no longer needs to load partition constraints, because even update/delete now use partprune.c's facilities * relation_excluded_by_constraints no longer needs to worry about fielding update/delete on partitioned tablesm, which in turn means InheritanceKind is no longer necessary --- src/backend/nodes/outfuncs.c | 1 - src/backend/optimizer/plan/createplan.c | 10 ----- src/backend/optimizer/plan/planner.c | 8 ---- src/backend/optimizer/prep/prepjointree.c | 1 - src/backend/optimizer/util/plancat.c | 73 ++++--------------------------- src/include/nodes/pathnodes.h | 15 ------- 6 files changed, 9 insertions(+), 99 deletions(-) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index f97cf37f1f..9b91d59348 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2210,7 +2210,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); WRITE_UINT_FIELD(qual_security_level); - WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind); WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasHavingQual); diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 1b4f7db649..496c560cad 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -2049,12 +2049,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) /* * During setrefs.c, we'll need the grouping_map to fix up the cols lists * in GroupingFunc nodes. Save it for setrefs.c to use. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be grouping in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->grouping_map == NULL); root->grouping_map = grouping_map; @@ -2211,12 +2206,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) * with InitPlan output params. (We can't just do that locally in the * MinMaxAgg node, because path nodes above here may have Agg references * as well.) Save the mmaggregates list to tell setrefs.c to do that. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be aggregates in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->minmax_aggs == NIL); root->minmax_aggs = best_path->mmaggregates; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 33fbfa5f37..1a3b0540d6 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -636,7 +636,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->grouping_map = NULL; root->minmax_aggs = NIL; root->qual_security_level = 0; - root->inhTargetKind = INHKIND_NONE; root->hasRecursion = hasRecursion; if (hasRecursion) root->wt_param_id = assign_special_exec_param(root); @@ -1311,13 +1310,6 @@ inheritance_planner(PlannerInfo *root) parent_rte->securityQuals = NIL; /* - * Mark whether we're planning a query to a partitioned table or an - * inheritance parent. - */ - subroot->inhTargetKind = - (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED; - - /* * Ignore a partitioned child. Instead, the paths of its children will * be added to subpaths. */ diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index aebe162713..8aef1f6535 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -894,7 +894,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->grouping_map = NULL; subroot->minmax_aggs = NIL; subroot->qual_security_level = 0; - subroot->inhTargetKind = INHKIND_NONE; subroot->hasRecursion = false; subroot->wt_param_id = -1; subroot->non_recursive_path = NULL; diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 3efa1bdc1a..f2ee83c81b 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1264,36 +1264,6 @@ get_relation_constraints(PlannerInfo *root, } } - /* - * Append partition predicates, if any. - * - * For selects, partition pruning uses the parent table's partition bound - * descriptor, instead of constraint exclusion which is driven by the - * individual partition's partition constraint. - */ - if (enable_partition_pruning && root->parse->commandType != CMD_SELECT) - { - List *pcqual = RelationGetPartitionQual(relation); - - if (pcqual) - { - /* - * Run the partition quals through const-simplification similar to - * check constraints. We skip canonicalize_qual, though, because - * partition quals should be in canonical form already; also, - * since the qual is in implicit-AND format, we'd have to - * explicitly convert it to explicit-AND format and back again. - */ - pcqual = (List *) eval_const_expressions(root, (Node *) pcqual); - - /* Fix Vars to have the desired varno */ - if (varno != 1) - ChangeVarNodes((Node *) pcqual, 1, varno, 0); - - result = list_concat(result, pcqual); - } - } - table_close(relation, NoLock); return result; @@ -1417,40 +1387,15 @@ relation_excluded_by_constraints(PlannerInfo *root, /* * Skip further tests, depending on constraint_exclusion. */ - switch (constraint_exclusion) - { - case CONSTRAINT_EXCLUSION_OFF: - - /* - * Don't prune if feature turned off -- except if the relation is - * a partition. While partprune.c-style partition pruning is not - * yet in use for all cases (update/delete is not handled), it - * would be a UI horror to use different user-visible controls - * depending on such a volatile implementation detail. Therefore, - * for partitioned tables we use enable_partition_pruning to - * control this behavior. - */ - if (root->inhTargetKind == INHKIND_PARTITIONED) - break; - return false; - - case CONSTRAINT_EXCLUSION_PARTITION: - - /* - * When constraint_exclusion is set to 'partition' we only handle - * OTHER_MEMBER_RELs, or BASERELs in cases where the result target - * is an inheritance parent or a partitioned table. - */ - if ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) && - !(rel->reloptkind == RELOPT_BASEREL && - root->inhTargetKind != INHKIND_NONE && - rel->relid == root->parse->resultRelation)) - return false; - break; - - case CONSTRAINT_EXCLUSION_ON: - break; /* always try to exclude */ - } + if (constraint_exclusion == CONSTRAINT_EXCLUSION_OFF) + return false; + /* + * When constraint_exclusion is set to 'partition' we only handle + * OTHER_MEMBER_RELs. + */ + else if (constraint_exclusion == CONSTRAINT_EXCLUSION_PARTITION && + rel->reloptkind != RELOPT_OTHER_MEMBER_REL) + return false; /* * Check for self-contradictory restriction clauses. We dare not make diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 0b8e90951a..fe616711e7 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -82,18 +82,6 @@ typedef enum UpperRelationKind /* NB: UPPERREL_FINAL must be last enum entry; it's used to size arrays */ } UpperRelationKind; -/* - * This enum identifies which type of relation is being planned through the - * inheritance planner. INHKIND_NONE indicates the inheritance planner - * was not used. - */ -typedef enum InheritanceKind -{ - INHKIND_NONE, - INHKIND_INHERITED, - INHKIND_PARTITIONED -} InheritanceKind; - /*---------- * PlannerGlobal * Global information for planning/optimization @@ -325,9 +313,6 @@ struct PlannerInfo Index qual_security_level; /* minimum security_level for quals */ /* Note: qual_security_level is zero if there are no securityQuals */ - InheritanceKind inhTargetKind; /* indicates if the target relation is an - * inheritance child or partition or a - * partitioned table */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */ bool hasHavingQual; /* true if havingQual was non-null */ -- 2.11.0
v21-0004-Lazy-creation-of-RTEs-for-inheritance-children.patch (text/plain, 94.6K) download From 2e18e7b89ab581d43238e1c4ea99370fddea8e56 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 26 Oct 2018 16:45:59 +0900 Subject: [PATCH v21 4/6] Lazy creation of RTEs for inheritance children Currently, expand_inherited_tables called from subquery_planner opens all inheritance child tables and adds them to the query's range table and PlannerInfo in the form of AppendRelInfo's initially and later RelOptInfo's. For partitioning, that's pretty wasteful, because it's possible to determine only the partitions that will need to be scanned using partition pruning, which doesn't require opening the partitions themselves. However, the point at which expand_inherited_tables is called currently is too early to perform partition pruning. This commit rearranges things within the planner so that expand_inherited_tables can be called from query_planner after the latter has finished setting up the information needed to perform partition pruning. However that means the PlannerInfo arrays that store RangeTblEntry's, RelOptInfo's, and AppendRelInfo's need to be expanded if inheritance expansion leads to adding more relations to planning. There are also consequences around targetlist expansion. For example, preprocess_targetlist cannot add the junk columns entries needed for row mark handling until all of the inheritance children have been determined, because different children may need different types of junk columns to be added. So, the approach of late inheritance expansion required some interface tweaks for preprocess_targetlist(). For partitioning, although we don't create a RangeTblEntry and RelOptInfo for pruned partitions at make_one_rel time, partitionwise join code relies on the fact that even though partitions may have been pruned, they'd still own a RelOptInfo to handle the outer join case where the pruned partition appears on the nullable side of join. Partitionwise join code deals with that by allocating dummy RelOptInfos for pruned partitions that are based mostly on their parent's properties. There are some regression test diffs: 1. Caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child, as seen in the partition_aggregate.out test output. 2. Those in postgres_fdw.out are caused by the fact that junk columns required for row marking are added to reltarget->exprs later than user columns, because the row marking junk columns aren't added until the inheritance is expanded which as of this commit is later than it used to be as noted above. --- contrib/postgres_fdw/expected/postgres_fdw.out | 32 +- src/backend/nodes/outfuncs.c | 1 + src/backend/optimizer/path/allpaths.c | 239 +----- src/backend/optimizer/path/joinrels.c | 61 ++ src/backend/optimizer/plan/initsplan.c | 49 -- src/backend/optimizer/plan/planmain.c | 10 + src/backend/optimizer/plan/planner.c | 64 +- src/backend/optimizer/plan/setrefs.c | 6 + src/backend/optimizer/prep/preptlist.c | 15 +- src/backend/optimizer/prep/prepunion.c | 3 + src/backend/optimizer/util/appendinfo.c | 45 +- src/backend/optimizer/util/inherit.c | 843 ++++++++++++++++------ src/backend/optimizer/util/plancat.c | 47 +- src/backend/optimizer/util/relnode.c | 107 +-- src/backend/partitioning/partprune.c | 44 +- src/include/nodes/pathnodes.h | 19 +- src/include/optimizer/appendinfo.h | 7 +- src/include/optimizer/pathnode.h | 1 + src/include/optimizer/plancat.h | 4 +- src/include/optimizer/prep.h | 3 +- src/test/regress/expected/partition_aggregate.out | 4 +- 21 files changed, 950 insertions(+), 654 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 029552b152..0c417cd360 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7128,15 +7128,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7166,15 +7166,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7203,15 +7203,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid @@ -7221,15 +7221,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (39 rows) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 9b91d59348..bd2ddb8c2a 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2270,6 +2270,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_BOOL_FIELD(consider_partitionwise_join); WRITE_BITMAPSET_FIELD(top_parent_relids); WRITE_NODE_FIELD(partitioned_child_rels); + WRITE_UINT_FIELD(inh_root_parent); } static void diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 4bbd9aa84d..f4c77821fe 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -45,7 +45,6 @@ #include "optimizer/tlist.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -145,9 +144,6 @@ static void subquery_push_qual(Query *subquery, static void recurse_push_qual(Node *setOp, Query *topquery, RangeTblEntry *rte, Index rti, Node *qual); static void remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel); -static bool apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, - RelOptInfo *childrel, - RangeTblEntry *childRTE, AppendRelInfo *appinfo); /* @@ -388,6 +384,14 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, else if (rte->inh) { /* + * expand_inherited_tables may have proved that the relation is empty. + * For example, if it's a partitioned table with 0 partitions or all + * of its partitions are pruned. In that case nothing to do here. + */ + if (IS_DUMMY_REL(rel)) + return; + + /* * If it's a target relation, set the sizes of children instead. * Otherwise, we'll append the outputs of children, so process it as * an "append relation". @@ -968,29 +972,12 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, int parentRTindex = rti; bool has_live_children = false; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); Assert(IS_SIMPLE_REL(rel)); - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - foreach(l, root->append_rel_list) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); @@ -1001,7 +988,6 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, ListCell *lc; List *translated_exprs, *child_target_exprs; - bool childpruned; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1009,32 +995,23 @@ set_inherit_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); + Assert(childrel != NULL); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - childpruned = (did_pruning && - !bms_is_member(appinfo->child_relid, live_children)); - /* - * Unless the child is pruned, 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 baserestrictinfo quals. + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals or quals that contradict its + * constraints. */ - if (childpruned || - !apply_child_basequals(root, rel, childrel, childRTE, appinfo) || - relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* This partition needn't be scanned; skip it. */ - set_dummy_rel_pathlist(childrel); + if (IS_DUMMY_REL(childrel)) continue; - } /* * Add missing Vars to child's reltarget. @@ -1179,8 +1156,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -1188,32 +1163,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Assert(IS_SIMPLE_REL(rel)); /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, we will bubble up - * the indexes of partitioned relations that appear down in the tree, so - * that when we've created Paths for all the children, the root - * partitioned table's list will contain all such indexes. - */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - rel->partitioned_child_rels = list_make1_int(rti); - - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - - /* * If this is a partitioned baserel, set the consider_partitionwise_join * flag; currently, we only consider partitionwise joins with the baserel * if its targetlist doesn't contain a whole-row Var. @@ -1252,7 +1201,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, RelOptInfo *childrel; ListCell *parentvars; ListCell *childvars; - bool childpruned; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1260,34 +1208,26 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); + Assert(childrel != NULL); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - childpruned = (did_pruning && - !bms_is_member(appinfo->child_relid, live_children)); /* - * Unless the child is pruned, 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 baserestrictinfo quals. + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals or quals that contradict its + * constraints. */ - if (childpruned || - !apply_child_basequals(root, rel, childrel, childRTE, appinfo) || - relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* This partition needn't be scanned; skip it. */ - set_dummy_rel_pathlist(childrel); + if (IS_DUMMY_REL(childrel)) continue; - } /* - * CE failed, so finish copying/modifying targetlist and join quals. + * Copy/Modify targetlist. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -2824,6 +2764,7 @@ inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) continue; childrel = find_base_rel(root, appinfo->child_relid); + Assert(childrel != NULL); /* Ignore excluded/pruned children. */ if (IS_DUMMY_REL(childrel)) @@ -3928,134 +3869,6 @@ generate_partitionwise_join_paths(PlannerInfo *root, RelOptInfo *rel) list_free(live_children); } -/* - * apply_child_basequals - * Populate childrel's quals based on rel's quals, translating them using - * appinfo. - * - * If any of the resulting clauses evaluate to false or NULL, we return false - * and don't apply any quals. Caller can mark the relation as a dummy rel in - * this case, since it needn't be scanned. - * - * If any resulting clauses evaluate to true, they're unnecessary and we don't - * apply then. - */ -static bool -apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, - RelOptInfo *childrel, RangeTblEntry *childRTE, - AppendRelInfo *appinfo) -{ - List *childquals; - Index cq_min_security; - ListCell *lc; - - /* - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities for - * const-simplification, and perhaps even pseudoconstant quals. Therefore, - * transform each RestrictInfo separately to see if it reduces to a - * constant or pseudoconstant. (We must process them separately to keep - * track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - return false; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. (Currently - * this can only happen in appendrels originating from UNION ALL; - * inheritance child tables don't have their own securityQuals, see - * expand_inherited_rtentry().) Pull any such securityQuals up into the - * baserestrictinfo for the child. This is similar to - * process_security_barrier_quals() for the parent rel, except that we - * can't make any general deductions from such quals, since they don't - * hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - return true; -} - /***************************************************************************** * DEBUG SUPPORT *****************************************************************************/ diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index a02321486f..6f77d2a0f4 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -21,6 +21,7 @@ #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" +#include "optimizer/tlist.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -52,6 +53,9 @@ static SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, Relids left_relids, Relids right_relids); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + int partidx); /* @@ -1384,6 +1388,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (IS_SIMPLE_REL(rel1) && child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts); + if (IS_SIMPLE_REL(rel1) && child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts); + /* * If a child table has consider_partitionwise_join=false, it means * that it's a dummy relation for which we skipped setting up tlist @@ -1680,3 +1689,55 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +static RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx) +{ + RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + AppendRelInfo *appinfo = make_append_rel_info(parent, parentrte, + parent->tupdesc, + parentrte->relid, + parent->reltype, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 2afc3f1dfe..5e8a8b1eee 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -419,7 +419,6 @@ void create_lateral_join_info(PlannerInfo *root) { bool found_laterals = false; - Relids prev_parents PG_USED_FOR_ASSERTS_ONLY = NULL; Index rti; ListCell *lc; @@ -618,54 +617,6 @@ create_lateral_join_info(PlannerInfo *root) bms_add_member(brel2->lateral_referencers, rti); } } - - /* - * Lastly, propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - * - * It's possible for child rels to have their own children, in which case - * the topmost parent's lateral info must be propagated all the way down. - * This code handles that case correctly so long as append_rel_list has - * entries for child relationships before grandchild relationships, which - * is an okay assumption right now, but we'll need to be careful to - * preserve it. The assertions below check for incorrect ordering. - */ - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); - RelOptInfo *parentrel = root->simple_rel_array[appinfo->parent_relid]; - RelOptInfo *childrel = root->simple_rel_array[appinfo->child_relid]; - - /* - * If we're processing a subquery of a query with inherited target rel - * (cf. inheritance_planner), append_rel_list may contain entries for - * tables that are not part of the current subquery and hence have no - * RelOptInfo. Ignore them. We can ignore dead rels, too. - */ - if (parentrel == NULL || !IS_SIMPLE_REL(parentrel)) - continue; - - /* Verify that children are processed before grandchildren */ -#ifdef USE_ASSERT_CHECKING - prev_parents = bms_add_member(prev_parents, appinfo->parent_relid); - Assert(!bms_is_member(appinfo->child_relid, prev_parents)); -#endif - - /* OK, propagate info down */ - Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - Assert(childrel->direct_lateral_relids == NULL); - childrel->direct_lateral_relids = parentrel->direct_lateral_relids; - Assert(childrel->lateral_relids == NULL); - childrel->lateral_relids = parentrel->lateral_relids; - Assert(childrel->lateral_referencers == NULL); - childrel->lateral_referencers = parentrel->lateral_referencers; - } } diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 0b1c8a2529..651dfb794c 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -284,6 +284,16 @@ query_planner(PlannerInfo *root, List *tlist, } /* + * Expand RT entries that represent inherited or partitioned tables. + * This will perform partition pruning on partitioned tables in the + * original range table and also recursively on any child partitioned + * tables that were added by the expansion of the original parent(s). + * As new entries are added to the range table, various arrays in the + * PlannerInfo will be expanded accordingly. + */ + expand_inherited_tables(root); + + /* * Add child subroots needed to use during planning for individual child * targets */ diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 1a3b0540d6..90572e95b0 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -25,6 +25,7 @@ #include "access/table.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -716,27 +717,24 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } /* * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). + * pullup (so that all non-inherited RTEs are present). */ preprocess_rowmarks(root); /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain RTE_RELATION entries, so it's OK (and marginally more efficient) - * to do it after checking for joins and other special RTEs. We must do - * this after pulling up subqueries, else we'd fail to handle inherited - * tables in subqueries. - */ - expand_inherited_tables(root); - - /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -1202,7 +1200,7 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr) * into the top-level PlannerInfo * * Finally, here we apply grouping_planner to each child join relation - * produced by set_inherit_target_rel_pathlists to so that its path produces + * produced by set_inherit_target_rel_pathlists so that its path produces * the desired targetlist. * * Returns nothing; the useful output is in the Paths we attach to @@ -1247,7 +1245,7 @@ inheritance_planner(PlannerInfo *root) root->unexpanded_tlist = list_copy(root->parse->targetList); /* We haven't expanded inheritance yet, so pass false. */ - tlist = preprocess_targetlist(root); + tlist = preprocess_targetlist(root, false); root->processed_tlist = tlist; qp_extra.tlist = tlist; qp_extra.activeWindows = NIL; @@ -1257,9 +1255,11 @@ inheritance_planner(PlannerInfo *root) /* * If it turned out during query planning that all the children are dummy * (pruned or excluded by constraints), no need to do the steps below. - * Let grouping_planner finish up the final path. + * For regular inheritance case, it's possible that we only need to modify + * the parent table. Let grouping_planner finish up the final path. */ - if (IS_DUMMY_REL(planned_rel)) + parent_rte = planner_rt_fetch(top_parentRTindex, root); + if (!parent_rte->inh || IS_DUMMY_REL(planned_rel)) { grouping_planner(root, false, planned_rel, 0.0); return; @@ -1275,7 +1275,6 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = planner_rt_fetch(top_parentRTindex, root); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1604,14 +1603,19 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, */ if (planned_rel == NULL) { - tlist = preprocess_targetlist(root); + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root, false); /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation - * so that we can transfer its decoration (resnames etc) to the - * topmost tlist of the finished Plan. + * We are now mostly done hacking up the query's targetlist. More + * columns might be added during query_planner after inheritance + * expansion in make_one_rel, because some children may require + * different column than the parent for row mark handling; see + * add_rowmark_junk_columns(). Most of the remaining planning + * work will be done with the PathTarget representation of tlists, + * but save aside the full representation so that we can transfer + * its decoration (resnames etc) to the topmost tlist of the + * finished Plan. */ root->processed_tlist = tlist; } @@ -2387,7 +2391,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2412,7 +2416,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } @@ -6849,6 +6853,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -6950,6 +6958,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 0213a37670..154ccda432 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -326,6 +326,12 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) UPPERREL_FINAL, NULL))) add_rtes_to_flat_rtable(rel->subroot, true); } + /* + * A NULL rel also means an unplanned subquery rte, so apply + * flatten_unplanned_rtes. + */ + else + flatten_unplanned_rtes(glob, rte); } rti++; } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 5392d1a561..41a57d16b2 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -65,9 +65,13 @@ static List *expand_targetlist(List *tlist, int command_type, * * As a side effect, if there's an ON CONFLICT UPDATE clause, its targetlist * is also preprocessed (and updated in-place). + * + * inheritance_expanded specifies whether inheritance tables are expanded + * as of calling this function. Caller must specify it because that affects + * which row marking related junk columns get added to the targetlist. */ List * -preprocess_targetlist(PlannerInfo *root) +preprocess_targetlist(PlannerInfo *root, bool inheritance_expanded) { Query *parse = root->parse; int result_relation = parse->resultRelation; @@ -134,6 +138,15 @@ preprocess_targetlist(PlannerInfo *root) if (rc->rti != rc->prti) continue; + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which will change the parent row + * mark's allMarkTypes fields. + */ + if (rc->isParent && !inheritance_expanded) + continue; + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) { /* Need to fetch TID */ diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 55eeb5127c..7bed914f35 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -31,12 +31,15 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" #include "optimizer/cost.h" +#include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index 7447853db9..c72251aea2 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -33,10 +33,10 @@ typedef struct AppendRelInfo **appinfos; } adjust_appendrel_attrs_context; -static void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, adjust_appendrel_attrs_context *context); static List *adjust_inherited_tlist(List *tlist, @@ -48,18 +48,20 @@ static List *adjust_inherited_tlist(List *tlist, * Build an AppendRelInfo for the parent-child pair */ AppendRelInfo * -make_append_rel_info(Relation parentrel, Relation childrel, - Index parentRTindex, Index childRTindex) +make_append_rel_info(RelOptInfo *parent, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childtype, + Index childRTindex) { AppendRelInfo *appinfo = makeNode(AppendRelInfo); - appinfo->parent_relid = parentRTindex; + appinfo->parent_relid = parent->relid; appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->rd_rel->reltype; - appinfo->child_reltype = childrel->rd_rel->reltype; - make_inh_translation_list(parentrel, childrel, childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = RelationGetRelid(parentrel); + appinfo->parent_reltype = parent->reltype; + appinfo->child_reltype = childtype; + make_inh_translation_list(parent->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; return appinfo; } @@ -72,14 +74,11 @@ make_append_rel_info(Relation parentrel, Relation childrel, * For paranoia's sake, we match type/collation as well as attribute name. */ static void -make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, - List **translated_vars) +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) { List *vars = NIL; - TupleDesc old_tupdesc = RelationGetDescr(oldrelation); - TupleDesc new_tupdesc = RelationGetDescr(newrelation); - Oid new_relid = RelationGetRelid(newrelation); int oldnatts = old_tupdesc->natts; int newnatts = new_tupdesc->natts; int old_attno; @@ -109,7 +108,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, * When we are generating the "translation list" for the parent table * of an inheritance set, no need to search for matches. */ - if (oldrelation == newrelation) + if (from_rel == to_rel) { vars = lappend(vars, makeVar(newvarno, (AttrNumber) (old_attno + 1), @@ -135,10 +134,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, { HeapTuple newtup; - newtup = SearchSysCacheAttName(new_relid, attname); + newtup = SearchSysCacheAttName(to_rel, attname); if (!newtup) elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; ReleaseSysCache(newtup); @@ -148,10 +147,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, /* Found it, check type and collation match */ if (atttypid != att->atttypid || atttypmod != att->atttypmod) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); if (attcollation != att->attcollation) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); vars = lappend(vars, makeVar(newvarno, (AttrNumber) (new_attno + 1), diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 323d8c07e2..70e42da2c1 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -18,33 +18,47 @@ #include "access/table.h" #include "catalog/partition.h" #include "catalog/pg_inherits.h" +#include "catalog/pg_type.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" #include "optimizer/inherit.h" +#include "optimizer/optimizer.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" +#include "optimizer/plancat.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" +#include "partitioning/partprune.h" #include "utils/rel.h" static PlannerInfo *create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo); -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); +static void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti); +static void expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, + RangeTblEntry *rte, Index rti, RelOptInfo *rel); static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); - +static RelOptInfo *build_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); +static bool apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *childrel, + RangeTblEntry *childRTE, AppendRelInfo *appinfo); /* * expand_inherited_tables @@ -52,37 +66,145 @@ static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, * into an "append relation". At the conclusion of this process, * the "inh" flag is set in all and only those RTEs that are append * relation parents. + * + * Note that although we're calling the combined output of inheritance set + * relations an "append relation" here, the caller may not always want to + * combine the relations. For example, if the parent of the inheritance + * set is the query's target relation, each child relation is processed + * on its own as the query's target relation. */ void expand_inherited_tables(PlannerInfo *root) { - Index nrtes; + int orig_rtable_size; Index rti; - ListCell *rl; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; /* * expand_inherited_rtentry may add RTEs to parse->rtable. The function is * expected to recursively handle any RTEs that it creates with inh=true. * So just scan as far as the original end of the rtable list. */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) + for (rti = 1; rti < orig_rtable_size; rti++) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + if (rte->inh) + expand_inherited_rtentry(root, brel, rte, rti); } } /* * expand_inherited_rtentry - * Check whether a rangetable entry represents an inheritance set. - * If so, add entries for all the child tables to the query's - * rangetable, and build AppendRelInfo nodes for all the child tables - * and add them to root->append_rel_list. If not, clear the entry's - * "inh" flag to prevent later code from looking for AppendRelInfos. + * This initializes RelOptInfos for inheritance child relations if the + * passed-in relation has any + * + * 'rel' is the parent relation, whose range table entry ('rte') has been + * marked to require adding children. Parent could either be a subquery (if + * we flattened UNION ALL query) or a table that's known to have (or once had) + * inheritance children. The latter consists of both regular inheritance + * parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as a child to be scanned as part of the + * inheritance set. The original RTE is considered to represent the whole + * inheritance set. + */ +static void +expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_inheritance_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_nonpartitioned_inherited_rtentry(root, rte, rti, rel); + } +} + +/* + * expand_nonpartitioned_inherited_rtentry + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. * * Note that the original RTE is considered to represent the whole * inheritance set. The first of the generated RTEs is an RTE for the same @@ -93,55 +215,35 @@ expand_inherited_tables(PlannerInfo *root) * regular inheritance, a parent RTE must always have at least two associated * AppendRelInfos: one corresponding to the parent table as a simple member of * inheritance set and one or more corresponding to the actual children. - * Since a partitioned table is not scanned, it might have only one associated - * AppendRelInfo. */ static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, + RangeTblEntry *rte, + Index rti, + RelOptInfo *rel) { Oid parentOID; PlanRowMark *oldrc; - Relation oldrelation; - LOCKMODE lockmode; + LOCKMODE lockmode = rte->rellockmode; List *inhOIDs; ListCell *l; + int num_children; + int num_children_added = 0; - /* Does RT entry allow inheritance? */ - if (!rte->inh) - return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } - /* Fast path for common case of childless table */ + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); parentOID = rte->relid; - if (!has_subclass(parentOID)) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * The rewriter should already have obtained an appropriate lock on each - * relation named in the query. However, for each child relation we add - * to the query, we must obtain an appropriate lock, because this will be - * the first use of those relations in the parse/rewrite/plan pipeline. - * Child rels should use the same lockmode as their parent. - */ - lockmode = rte->rellockmode; /* Scan for all members of inheritance set, acquire needed locks */ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); /* * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite above has_subclass() check, if table - * once had a child but no longer does. + * case. This could happen despite has_subclass() check performed by + * subquery_planner, if table once had a child but no longer does. */ - if (list_length(inhOIDs) < 2) + num_children = list_length(inhOIDs); + if (num_children < 2) { /* Clear flag before returning */ rte->inh = false; @@ -149,216 +251,221 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } /* - * If parent relation is selected FOR UPDATE/SHARE, we need to mark its - * PlanRowMark as isParent = true, and generate a new PlanRowMark for each - * child. + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. */ oldrc = get_plan_rowmark(root->rowMarks, rti); - if (oldrc) - oldrc->isParent = true; + Assert(oldrc == NULL || oldrc->isParent); /* - * Must open the parent relation to examine its tupdesc. We need not lock - * it; we assume the rewriter already did. + * Must expand PlannerInfo arrays by num_children before we can add + * children. */ - oldrelation = table_open(parentOID, NoLock); + Assert(num_children > 0); + expand_planner_arrays(root, num_children); - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) + foreach(l, inhOIDs) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); - - /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. - */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); - } - else - { - List *appinfos = NIL; + Oid childOID = lfirst_oid(l); + Relation newrelation; RangeTblEntry *childrte; Index childRTindex; + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. */ - foreach(l, inhOIDs) + if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = table_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - table_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - table_close(newrelation, NoLock); + table_close(newrelation, lockmode); + continue; } - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); + (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation, &childrte, + &childRTindex); + Assert(childrte != NULL); + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + Assert(childRTindex > 0); + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; } - table_close(oldrelation, NoLock); + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } } /* * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) + Index parentRTindex, RelOptInfo *parentrel) { + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; - check_stack_depth(); - - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + if (bms_num_members(partindexes) > 0) + expand_planner_arrays(root, bms_num_members(partindexes)); /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. */ - if (partdesc->nparts == 0) - { - parentrte->inh = false; - return; - } + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); - for (i = 0; i < partdesc->nparts; i++) + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) { - Oid childOID = partdesc->oids[i]; - Relation childrel; + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + RangeTblEntry *childrte; + Index childRTindex; /* Open rel; we already have required locks */ - childrel = table_open(childOID, NoLock); + newrelation = table_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation, + &childrte, &childRTindex); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + /* Close child relations, but keep locks */ + table_close(newrelation, NoLock); + + /* If the child is partitioned itself, expand it too. */ + if (childrel->part_scheme) + { + Assert(childrte->inh); expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); + childrel); + } + } - /* Close child relation, but keep locks */ - table_close(childrel, NoLock); + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); } } /* - * expand_single_inheritance_child - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) + * The return value is the RelOptInfo that's added. * * PlanRowMarks still carry the top-parent's RTI, and the top-parent's * allMarkTypes field still accumulates values from all descendents. * * "parentrte" and "parentRTindex" are immediate parent's RTE and * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. */ -static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); Oid childOID = RelationGetRelid(childrel); RangeTblEntry *childrte; Index childRTindex; AppendRelInfo *appinfo; + RelOptInfo *childrelopt; /* * Build an RTE for the child, and attach to query's rangetable list. We * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh = false. Also, set requiredPerms to zero since - * all required permissions checks are done on the original RTE. Likewise, - * set the child's securityQuals to empty, because we only want to apply - * the parent's RLS conditions regardless of what RLS properties + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties * individual children may have. (This is an intentional choice to make * inherited RLS work like regular permissions checks.) The parent * securityQuals will be propagated to children along with other base @@ -368,49 +475,42 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childrte_p = childrte; childrte->relid = childOID; childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) - childrte->inh = true; - else - childrte->inh = false; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); childrte->requiredPerms = 0; childrte->securityQuals = NIL; parse->rtable = lappend(parse->rtable, childrte); childRTindex = list_length(parse->rtable); *childRTindex_p = childRTindex; - /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. - */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) - { - appinfo = make_append_rel_info(parentrel, childrel, - parentRTindex, childRTindex); - *appinfos = lappend(*appinfos, appinfo); + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + RelationGetDescr(childrel), + RelationGetRelid(childrel), + RelationGetForm(childrel)->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); } /* @@ -442,6 +542,147 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need to create one, + * because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_inheritance_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; +} + +/* + * build_inheritance_child_rel + * Build a RelOptInfo for child relation of an inheritance set + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields based on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turn out to be self-contradictory, + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + * + * It's possible for child rels to have their own children, in which case + * the topmost parent's lateral info must be propagated all the way down. + * That's ensured by having childrel be expanded via this same path. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + */ + if (!apply_child_basequals(root, parent, childrel, childRTE, appinfo) || + relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; } /* @@ -594,7 +835,7 @@ create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) * column, filling placeholder entries for dropped columns, etc., all of * which occurs with the child's TupleDesc. */ - tlist = preprocess_targetlist(subroot); + tlist = preprocess_targetlist(subroot, true); subroot->processed_tlist = tlist; /* Add any newly added Vars to the child RelOptInfo. */ @@ -609,3 +850,131 @@ create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) return subroot; } + +/* + * apply_child_basequals + * Populate childrel's quals based on rel's quals, translating them using + * appinfo. + * + * If any of the resulting clauses evaluate to false or NULL, we return false + * and don't apply any quals. Caller can mark the relation as a dummy rel in + * this case, since it needn't be scanned. + * + * If any resulting clauses evaluate to true, they're unnecessary and we don't + * apply then. + */ +static bool +apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *childrel, RangeTblEntry *childRTE, + AppendRelInfo *appinfo) +{ + List *childquals; + Index cq_min_security; + ListCell *lc; + + /* + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities for + * const-simplification, and perhaps even pseudoconstant quals. Therefore, + * transform each RestrictInfo separately to see if it reduces to a + * constant or pseudoconstant. (We must process them separately to keep + * track of the security level of each qual.) + */ + childquals = NIL; + cq_min_security = UINT_MAX; + foreach(lc, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, + (Node *) rinfo->clause, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + return false; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + /* track minimum security level among child quals */ + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might have + * securityQuals associated with this particular child node. (Currently + * this can only happen in appendrels originating from UNION ALL; + * inheritance child tables don't have their own securityQuals, see + * expand_inherited_rtentry().) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except that we + * can't make any general deductions from such quals, since they don't + * hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* not likely that we'd see constants here, so no check */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, false, false, + security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* + * OK, we've got all the baserestrictinfo quals for this child. + */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + return true; +} diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index f2ee83c81b..f3f7bb29c6 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -105,20 +105,20 @@ static void set_baserel_partition_key_exprs(Relation relation, * important for it. */ void -get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) +get_relation_info(PlannerInfo *root, RangeTblEntry *rte, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; bool hasindex; List *indexinfos = NIL; + bool inhparent = rte->inh; /* * We need not lock the relation since it was already locked, either by * the rewriter or when expand_inherited_rtentry() added it to the query's * rangetable. */ - relation = table_open(relationObjectId, NoLock); + relation = heap_open(rte->relid, NoLock); /* Temporary and unlogged relations are inaccessible during recovery. */ if (!RelationNeedsWAL(relation) && RecoveryInProgress()) @@ -444,11 +444,32 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, get_relation_foreign_keys(root, rel, relation, inhparent); /* - * Collect info about relation's partitioning scheme, if any. Only - * inheritance parents may be partitioned. + * Collect some additional information for inheritance parents. */ - if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - set_relation_partition_info(root, rel, relation); + if (inhparent) + { + /* + * We'll need the TupleDesc when initializing the child relation. + * A copy is being made because concurrent changes might drop + * the relcache entry. That's possible because ALTER TABLE + * child_table NO INHERIT parent_table only requires an + * AccessShareLock on parent_table. + */ + rel->tupdesc = CreateTupleDescCopy(RelationGetDescr(relation)); + rel->reltype = RelationGetForm(relation)->reltype; + + /* + * If partitioned, also save the information of partitioning scheme, + * and whether the query updates any of the partition key columns. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + set_relation_partition_info(root, rel, relation); + root->partColsUpdated |= has_partition_attrs(relation, + rte->updatedCols, + NULL); + } + } table_close(relation, NoLock); @@ -458,7 +479,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * removing an index, or adding a hypothetical index to the indexlist. */ if (get_relation_info_hook) - (*get_relation_info_hook) (root, relationObjectId, inhparent, rel); + (*get_relation_info_hook) (root, rte->relid, rte->inh, rel); } /* @@ -1845,16 +1866,20 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; + + /* + * Since we must've taken a lock on the table, it's okay to simply copy + * the pointers to relcache data here. + */ + rel->part_oids = partdesc->oids; + rel->boundinfo = partdesc->boundinfo; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 4130514952..f6fca75677 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -132,6 +132,50 @@ setup_append_rel_array(PlannerInfo *root) } /* + * expand_planner_arrays + * Expand the PlannerInfo arrays by add_size members and initialize the + * the newly added bytes with zero + */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size; + + Assert(add_size > 0); + + new_size = root->simple_rel_array_size + add_size; + + /* Expand various arrays and 0-initialize added bytes. */ + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + + if (root->append_rel_array) + { + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + } + else + { + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + } + + root->simple_rel_array_size = new_size; +} + +/* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. */ @@ -195,6 +239,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->joininfo = NIL; rel->has_eclass_joins = false; rel->consider_partitionwise_join = false; /* might get changed later */ + rel->top_parent_relids = NULL; /* might be changed later */ rel->part_scheme = NULL; rel->nparts = 0; rel->boundinfo = NULL; @@ -203,6 +248,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->partexprs = NULL; rel->nullable_partexprs = NULL; rel->partitioned_child_rels = NIL; + rel->inh_root_parent = 0; /* might be changed later */ /* * Pass top parent's relids down the inheritance hierarchy. If the parent @@ -216,16 +262,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->top_parent_relids = parent->top_parent_relids; else rel->top_parent_relids = bms_copy(parent->relids); + + /* + * For inheritance child relations, we also set inh_root_parent. + * Note that 'parent' might itself be a child (a sub-partitioned + * partition), in which case we simply use its value of + * inh_root_parent. + */ + if (parent->rtekind == RTE_RELATION) + rel->inh_root_parent = parent->inh_root_parent > 0 ? + parent->inh_root_parent : + parent->relid; } - else - rel->top_parent_relids = NULL; /* Check type of rtable entry */ switch (rte->rtekind) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte, rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -273,52 +328,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); - /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. - */ - if (rte->inh) - { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); - - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; - } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); - } - return rel; } diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 8c9721935d..472a6cd331 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -45,6 +45,8 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "partitioning/partprune.h" @@ -433,17 +435,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -537,23 +545,20 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ @@ -561,6 +566,13 @@ prune_append_rel_partitions(RelOptInfo *rel) return NULL; /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. */ @@ -587,15 +599,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index fe616711e7..2973458890 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -15,6 +15,7 @@ #define PATHNODES_H #include "access/sdir.h" +#include "access/tupdesc.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/params.h" @@ -370,7 +371,6 @@ struct PlannerInfo }; - /* * In places where it's known that simple_rte_array[] must have been prepared * already, we just index into it to fetch RTEs. In code that might be @@ -723,11 +723,28 @@ typedef struct RelOptInfo int nparts; /* number of partitions */ struct PartitionBoundInfoData *boundinfo; /* Partition bounds */ List *partition_qual; /* partition constraint */ + Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ + + /* + * For inheritance children, this is the RT index of inheritance table + * mentioned in the query from which this relation originated. + * top_parent_relids cannot be used for this, because if the inheritance + * root table is itself under UNION ALL, top_parent_relids contains the + * RT index of UNION ALL parent subquery. + */ + Index inh_root_parent; + + /* + * Set only if this is an inheritance parent relation. This information + * is needed when initializing the planning info for children. + */ + TupleDesc tupdesc; /* A "copy" of the table's tuple desriptor */ + Oid reltype; /* Table's reltype */ } RelOptInfo; /* diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h index 790914c1b0..c0b8db9a62 100644 --- a/src/include/optimizer/appendinfo.h +++ b/src/include/optimizer/appendinfo.h @@ -17,9 +17,10 @@ #include "nodes/pathnodes.h" #include "utils/relcache.h" -extern AppendRelInfo *make_append_rel_info(Relation parentrel, - Relation childrel, - Index parentRTindex, Index childRTindex); +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parent, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childtype, + Index childRTindex); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index ef2c9b4728..730d9e7523 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -278,6 +278,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index 40f70f9f2b..fb370ed80e 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -25,8 +25,8 @@ typedef void (*get_relation_info_hook_type) (PlannerInfo *root, extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; -extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); +extern void get_relation_info(PlannerInfo *root, RangeTblEntry *rte, + RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index a9b2c9026c..27de05ba3e 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -34,7 +34,8 @@ extern Relids get_relids_for_join(Query *query, int joinrelid); /* * prototypes for preptlist.c */ -extern List *preprocess_targetlist(PlannerInfo *root); +extern List *preprocess_targetlist(PlannerInfo *root, + bool inheritance_expanded); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 6bc106831e..1450cef057 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) -- 2.11.0
v21-0005-Teach-planner-to-only-process-unpruned-partition.patch (text/plain, 6.9K) download From 9afa6040bdddcf6d4a1fc3aaa9bf338027ba3fd4 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 15 Oct 2018 10:59:24 +0900 Subject: [PATCH v21 5/6] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/inherit.c | 1 + src/backend/optimizer/util/relnode.c | 3 +++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/pathnodes.h | 4 ++++ 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 6f77d2a0f4..fdbf7689ed 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1452,6 +1452,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 90572e95b0..08c472aff4 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6845,7 +6845,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -6853,9 +6855,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -6936,7 +6936,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -6948,7 +6947,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -6958,9 +6959,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 70e42da2c1..fa719a0222 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -365,6 +365,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, /* Perform pruning. */ partindexes = prune_append_rel_partitions(parentrel); + parentrel->live_parts = partindexes; /* Must expand PlannerInfo arrays before we can add children. */ if (bms_num_members(partindexes) > 0) diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index f6fca75677..03c9ded294 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1743,6 +1743,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 472a6cd331..babb7d3406 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -438,29 +438,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 2973458890..8e3d2dd736 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -726,6 +726,10 @@ typedef struct RelOptInfo Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Indexes into part_rels of the non-NULL + * RelOptInfos of unpruned partitions; exists + * to avoid having to iterate over the entire + * part_rels array to filter NULL entries. */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.11.0
v21-0006-Do-not-lock-all-partitions-at-the-beginning.patch (text/plain, 1.9K) download From d98292ebd9a4470e2ab9f5cc01a08b559c4e46cc Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 12 Oct 2018 19:12:53 +0900 Subject: [PATCH v21 6/6] Do not lock all partitions at the beginning --- src/backend/optimizer/util/inherit.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index fa719a0222..9c4465c014 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -332,10 +332,6 @@ expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, * * Partitions are added to the query in order in which they are found in * the parent's PartitionDesc. - * - * Note: even though only the unpruned partitions will be added to the - * resulting plan, this still locks *all* partitions via find_all_inheritors - * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, @@ -349,10 +345,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, parentrel->inh_root_parent : parentRTindex; - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - /* * Initialize partitioned_child_rels to contain this RT index. * @@ -389,8 +381,11 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, RangeTblEntry *childrte; Index childRTindex; - /* Open rel; we already have required locks */ - newrelation = table_open(childOID, NoLock); + /* + * Open rel; this's the first time of opening partitions for this + * query, so take the appropriate locks. + */ + newrelation = table_open(childOID, lockmode); Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-08 09:27  David Rowley <david.rowley@2ndquadrant.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 2 replies; 265+ messages in thread

From: David Rowley @ 2019-02-08 09:27 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Amit Langote <amitlangote09@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On Fri, 8 Feb 2019 at 22:12, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
> 0001 is now a patch to remove duplicate code from set_append_rel_size.  It
> combines multiple blocks that have the same body doing
> set_dummy_rel_pathlist().
>
> 0002 is the "overhaul inherited update/delete planning"

I had started looking at v20's 0001.  I've not done a complete pass
over it yet, but I'll likely just start again since v21 is out now:

I've removed the things you've fixed in v21. I think most of these
will apply to the 0002 patch, apart form maybe #2.

1. In set_rel_pathlist(), I wonder if you should be skipping the
set_rel_pathlist_hook call when inherited_update is true.

Another comment mentions:

* not this rel.  Also, this rel's sole path (ModifyTable) will be set
* by inheritance_planner later, so we can't check its paths yet.

So you're adding any paths for this rel


2. The comment here mentions "partition", but it might just be a child
of an inheritance parent:

if (childpruned ||
!apply_child_basequals(root, rel, childrel, childRTE, appinfo) ||
relation_excluded_by_constraints(root, childrel, childRTE))
{
/* This partition needn't be scanned; skip it. */
set_dummy_rel_pathlist(childrel);
continue;
}

This occurs in both set_inherit_target_rel_sizes() and set_append_rel_size()

3. I think the following comment:

/* If the child itself is partitioned it may turn into a dummy rel. */

It might be better to have it as:

/*
* If the child is a partitioned table it may have been marked
* as a dummy rel from having all its partitions pruned.
*/

I mostly think that by saying "if the child itself is partitioned",
then you're implying that we're currently operating on a partitioned
table, but we might be working on an inheritance parent.

4. In set_inherit_target_rel_pathlists(), you have:

/*
* If set_append_rel_size() decided the parent appendrel was
* parallel-unsafe at some point after visiting this child rel, we
* need to propagate the unsafety marking down to the child, so that
* we don't generate useless partial paths for it.
*/
if (!rel->consider_parallel)
childrel->consider_parallel = false;

But I don't quite see why set_append_rel_size() would have ever been
called for that rel. It should have been
set_inherit_target_rel_sizes(). It seems like the entire chunk can be
removed since set_inherit_target_rel_sizes() does not set
consider_parallel.

5. In planner.c you mention:

* inherit_make_rel_from_joinlist - this translates the jointree, replacing
 * the target relation in the original jointree (the root parent) by
 * individual child target relations and performs join planning on the
 * resulting join tree, saving the RelOptInfos of resulting join relations
 * into the top-level PlannerInfo


I can't see a function named inherit_make_rel_from_joinlist().

6. No such function:

* First, save the unexpanded version of the query's targetlist.
* create_inherit_target_child_root will use it as base when expanding
* it for a given child relation as the query's target relation instead
* of the parent.
*/

and in:

/*
* Add missing Vars to child's reltarget.
*
* create_inherit_target_child_root() would've added only those that
* are needed to be present in the top-level tlist (or ones that
* preprocess_targetlist thinks are needed to be in the tlist.)  We
* may need other attributes such as those contained in WHERE clauses,
* which are already computed for the parent during
* deconstruct_jointree processing of the original query (child's
* query never goes through deconstruct_jointree.)
*/

7. Where is "false" being passed here?

/* We haven't expanded inheritance yet, so pass false. */
tlist = preprocess_targetlist(root);
root->processed_tlist = tlist;
qp_extra.tlist = tlist;
qp_extra.activeWindows = NIL;
qp_extra.groupClause = NIL;
planned_rel = query_planner(root, tlist, standard_qp_callback, &qp_extra);

-- 
 David Rowley                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-08 12:22  David Rowley <david.rowley@2ndquadrant.com>
  parent: David Rowley <david.rowley@2ndquadrant.com>
  1 sibling, 0 replies; 265+ messages in thread

From: David Rowley @ 2019-02-08 12:22 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Amit Langote <amitlangote09@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On Fri, 8 Feb 2019 at 22:27, David Rowley <david.rowley@2ndquadrant.com> wrote:
> I had started looking at v20's 0001.  I've not done a complete pass
> over it yet, but I'll likely just start again since v21 is out now:

I've now done a complete pass over v21.

Here's what I wrote down.

8. Is this code in the wrong patch? I don't see any function named
build_dummy_partition_rel in this patch.

* Make child entries in the EquivalenceClass as well.  If the childrel
* appears to be a dummy one (one built by build_dummy_partition_rel()),
* no need to make any new entries, because anything that'd need those
* can instead use the parent's (rel).
*/
if (childrel->relid != rel->relid &&

9. "to use" seems out of place here. It makes more sense if you remove
those words.

* Add child subroots needed to use during planning for individual child
* targets

10. Is this comment really needed?

/*
* This is needed, because inheritance_make_rel_from_joinlist needs to
* translate root->join_info_list executing make_rel_from_joinlist for a
* given child.
*/

None of the other node types mention what they're used for.  Seems
like something that might get outdated pretty quickly.

11. adjust_appendrel_attrs_mutator: This does not seem very robust:

/*
* No point in searching if parent rel not mentioned in eclass; but we
* can't tell that for sure if parent rel is itself a child.
*/
for (cnt = 0; cnt < nappinfos; cnt++)
{
if (bms_is_member(appinfos[cnt]->parent_relid, ec->ec_relids))
{
appinfo = appinfos[cnt];
break;
}
}

What if the caller passes multiple appinfos and actually wants them
all processed?  You'll only process the first one you find that has an
eclass member.  I think you should just loop over each appinfo and
process all the ones that have a match, not just the first.

I understand the current caller only passes 1, but I don't think that
gives you an excuse to take a shortcut on the implementation.  I think
probably you've done this as that's what is done for Var in
adjust_appendrel_attrs_mutator(), but a Var can only belong to a
single relation. An EquivalenceClass can have members for multiple
relations.


13. adjust_appendrel_attrs_mutator: This seems wrong:

/*
* We have found and replaced the parent expression, so done
* with EC.
*/
break;

Surely there could be multiple members for the parent. Say:

UPDATE parted SET ... WHERE x = y AND y = 1;

14. adjust_appendrel_attrs_mutator: Comment is wrong. There's no
function called adjust_inherited_target_child_root and the EC is
copied in the function, not the caller.

/*
* Now fix up EC's relids set.  It's OK to modify EC like this,
* because caller must have made a copy of the original EC.
* For example, see adjust_inherited_target_child_root.
*/


15. I don't think "Copy it before modifying though." should be part of
this comment.

/*
* Adjust all_baserels to replace the original target relation with the
* child target relation.  Copy it before modifying though.
*/
subroot->all_baserels = adjust_child_relids(subroot->all_baserels,
1, &appinfo);

-- 
 David Rowley                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-12 06:41  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: David Rowley <david.rowley@2ndquadrant.com>
  1 sibling, 2 replies; 265+ messages in thread

From: Amit Langote @ 2019-02-12 06:41 UTC (permalink / raw)
  To: David Rowley <david.rowley@2ndquadrant.com>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Amit Langote <amitlangote09@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Thanks for the review.

On 2019/02/08 18:27, David Rowley wrote:
> On Fri, 8 Feb 2019 at 22:12, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> 0001 is now a patch to remove duplicate code from set_append_rel_size.  It
>> combines multiple blocks that have the same body doing
>> set_dummy_rel_pathlist().
>>
>> 0002 is the "overhaul inherited update/delete planning"
> 
> I had started looking at v20's 0001.  I've not done a complete pass
> over it yet, but I'll likely just start again since v21 is out now:
> 
> I've removed the things you've fixed in v21. I think most of these
> will apply to the 0002 patch, apart form maybe #2.
> 
> 1. In set_rel_pathlist(), I wonder if you should be skipping the
> set_rel_pathlist_hook call when inherited_update is true.
> 
> Another comment mentions:
> 
> * not this rel.  Also, this rel's sole path (ModifyTable) will be set
> * by inheritance_planner later, so we can't check its paths yet.
> 
> So you're adding any paths for this rel

I've changed this part such that set_rel_pathlist returns right after the
last else{...} block if inherited_update is true.

> 2. The comment here mentions "partition", but it might just be a child
> of an inheritance parent:
> 
> if (childpruned ||
> !apply_child_basequals(root, rel, childrel, childRTE, appinfo) ||
> relation_excluded_by_constraints(root, childrel, childRTE))
> {
> /* This partition needn't be scanned; skip it. */
> set_dummy_rel_pathlist(childrel);
> continue;
> }
> 
> This occurs in both set_inherit_target_rel_sizes() and set_append_rel_size()

Fixed.

> 3. I think the following comment:
> 
> /* If the child itself is partitioned it may turn into a dummy rel. */
> 
> It might be better to have it as:
> 
> /*
> * If the child is a partitioned table it may have been marked
> * as a dummy rel from having all its partitions pruned.
> */
> 
> I mostly think that by saying "if the child itself is partitioned",
> then you're implying that we're currently operating on a partitioned
> table, but we might be working on an inheritance parent.

Agreed.  Your suggested wording is clearer, so rewrote the comment that way.

> 4. In set_inherit_target_rel_pathlists(), you have:
> 
> /*
> * If set_append_rel_size() decided the parent appendrel was
> * parallel-unsafe at some point after visiting this child rel, we
> * need to propagate the unsafety marking down to the child, so that
> * we don't generate useless partial paths for it.
> */
> if (!rel->consider_parallel)
> childrel->consider_parallel = false;
> 
> But I don't quite see why set_append_rel_size() would have ever been
> called for that rel. It should have been
> set_inherit_target_rel_sizes(). It seems like the entire chunk can be
> removed since set_inherit_target_rel_sizes() does not set
> consider_parallel.

This is a copy-pasted bit of code that's apparently useless.  Removed.

> 5. In planner.c you mention:
> 
> * inherit_make_rel_from_joinlist - this translates the jointree, replacing
>  * the target relation in the original jointree (the root parent) by
>  * individual child target relations and performs join planning on the
>  * resulting join tree, saving the RelOptInfos of resulting join relations
>  * into the top-level PlannerInfo
> 
> 
> I can't see a function named inherit_make_rel_from_joinlist().
> 
> 6. No such function:
> 
> * First, save the unexpanded version of the query's targetlist.
> * create_inherit_target_child_root will use it as base when expanding
> * it for a given child relation as the query's target relation instead
> * of the parent.
> */
> 
> and in:
> 
> /*
> * Add missing Vars to child's reltarget.
> *
> * create_inherit_target_child_root() would've added only those that
> * are needed to be present in the top-level tlist (or ones that
> * preprocess_targetlist thinks are needed to be in the tlist.)  We
> * may need other attributes such as those contained in WHERE clauses,
> * which are already computed for the parent during
> * deconstruct_jointree processing of the original query (child's
> * query never goes through deconstruct_jointree.)
> */

Oops, fixed.  So, these are the new functions:

set_inherit_target_rel_sizes
set_inherit_target_rel_pathlists

add_inherited_target_child_roots
create_inherited_target_child_root

inheritance_make_rel_from_joinlist

I've renamed set_inherit_target_* functions to set_inherited_target_* to
avoid having multiple styles of naming inheritance-related functions.

> 7. Where is "false" being passed here?
> 
> /* We haven't expanded inheritance yet, so pass false. */
> tlist = preprocess_targetlist(root);
> root->processed_tlist = tlist;
> qp_extra.tlist = tlist;
> qp_extra.activeWindows = NIL;
> qp_extra.groupClause = NIL;
> planned_rel = query_planner(root, tlist, standard_qp_callback, &qp_extra);

Hmm, thought I'd fixed this but maybe messed up again while rebasing.

> 8. Is this code in the wrong patch? I don't see any function named
> build_dummy_partition_rel in this patch.
>
> * Make child entries in the EquivalenceClass as well.  If the childrel
> * appears to be a dummy one (one built by build_dummy_partition_rel()),
> * no need to make any new entries, because anything that'd need those
> * can instead use the parent's (rel).
> */
> if (childrel->relid != rel->relid &&

Again, appears to be a rebasing mistake.  Moved that hunk to the "Lazy
creation of..." patch which necessitates it.

> 9. "to use" seems out of place here. It makes more sense if you remove
> those words.
>
> * Add child subroots needed to use during planning for individual child
> * targets

Removed.

> 10. Is this comment really needed?
>
> /*
> * This is needed, because inheritance_make_rel_from_joinlist needs to
> * translate root->join_info_list executing make_rel_from_joinlist for a
> * given child.
> */
>
> None of the other node types mention what they're used for.  Seems
> like something that might get outdated pretty quickly.

OK, removed.

> 11. adjust_appendrel_attrs_mutator: This does not seem very robust:
>
> /*
> * No point in searching if parent rel not mentioned in eclass; but we
> * can't tell that for sure if parent rel is itself a child.
> */
> for (cnt = 0; cnt < nappinfos; cnt++)
> {
> if (bms_is_member(appinfos[cnt]->parent_relid, ec->ec_relids))
> {
> appinfo = appinfos[cnt];
> break;
> }
> }
>
> What if the caller passes multiple appinfos and actually wants them
> all processed?  You'll only process the first one you find that has an
> eclass member.  I think you should just loop over each appinfo and
> process all the ones that have a match, not just the first.
>
> I understand the current caller only passes 1, but I don't think that
> gives you an excuse to take a shortcut on the implementation.  I think
> probably you've done this as that's what is done for Var in
> adjust_appendrel_attrs_mutator(), but a Var can only belong to a
> single relation. An EquivalenceClass can have members for multiple
> relations.

OK, I've refactored the code such that translation is carried out with
*all* appinfos passed to adjust_appendrel_attrs_mutator.

> 13. adjust_appendrel_attrs_mutator: This seems wrong:
>
> /*
> * We have found and replaced the parent expression, so done
> * with EC.
> */
> break;
>
> Surely there could be multiple members for the parent. Say:
>
> UPDATE parted SET ... WHERE x = y AND y = 1;

I hadn't considered that.  Removed the break so that *all* members of a
given EC are considered for translating.

> 14. adjust_appendrel_attrs_mutator: Comment is wrong. There's no
> function called adjust_inherited_target_child_root and the EC is
> copied in the function, not the caller.
>
> /*
> * Now fix up EC's relids set.  It's OK to modify EC like this,
> * because caller must have made a copy of the original EC.
> * For example, see adjust_inherited_target_child_root.
> */
>
>
> 15. I don't think "Copy it before modifying though." should be part of
> this comment.
>
> /*
> * Adjust all_baserels to replace the original target relation with the
> * child target relation.  Copy it before modifying though.
> */
> subroot->all_baserels = adjust_child_relids(subroot->all_baserels,
> 1, &appinfo);

Updated both of these stale comments.

Attached updated patches.

Thanks,
Amit

Attachments: download all (6 files, .tar.gz)

v22-0001-Reduce-code-duplication-in-set_append_rel_size.patch (text/plain, 2.9K) download From 9b13ec088b6aef9d4633f14d99e6de9770272fc3 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 8 Feb 2019 15:06:25 +0900 Subject: [PATCH v22 1/6] Reduce code-duplication in set_append_rel_size The code to mark a child rel dummy is repeated for 3 cases: * child is pruned, * contradictory quals found in apply_child_quals, * child excluded due to constraints. This combines the three cases in one if statement. --- src/backend/optimizer/path/allpaths.c | 39 +++++++++++------------------------ 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 0debac75c6..72184b0cf9 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -1018,6 +1018,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, RelOptInfo *childrel; ListCell *parentvars; ListCell *childvars; + bool childpruned; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1033,36 +1034,20 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - + childpruned = (did_pruning && + !bms_is_member(appinfo->child_relid, live_children)); /* - * 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 - * baserestrictinfo quals. + * Unless the child is pruned, 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 baserestrictinfo quals. */ - if (!apply_child_basequals(root, rel, childrel, childRTE, appinfo)) + if (childpruned || + !apply_child_basequals(root, rel, childrel, childRTE, appinfo) || + relation_excluded_by_constraints(root, childrel, childRTE)) { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - continue; - } - - if (relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* - * This child need not be scanned, so we can omit it from the - * appendrel. - */ + /* This child need not be scanned; skip it. */ set_dummy_rel_pathlist(childrel); continue; } -- 2.11.0
v22-0002-Overhaul-inheritance-update-delete-planning.patch (text/plain, 63.6K) download From 3dcab61f08abc5388194f10c8311a2a38ee3b058 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 8 Feb 2019 16:16:38 +0900 Subject: [PATCH v22 2/6] Overhaul inheritance update/delete planning With the current method, inheritance_planner, which handles the planning for update/delete commands targeting inheritance trees, repeatedly calls query_planner for each child table. That's inefficient for two reasons: 1. partprune.c cannot be used to perform partition pruning, because it can only be invoked from query_planner. With the current approach, query_planner only sees the individual partitions in the query tree it receives, not the parent partitioned table. This leaves each partition to be pruned using constraint exclusion. (constraint exclusion cannon prune hash partitions which means UPDATE/DELETE don't support hash partition pruning.) 2. Repeated invocation of query_planner results in allocating large amounts of memory, especially if there are many child tables. Also, it involves repeatedly performing the same processing, such as jointree processing. This commit addresses both of the issues by modifying query_planner to handle the inheritance target case directly. Instead of having the caller create separate PlannerInfo's for individual child target relations and pass them to query_planner (actually, via grouping_planner), this teaches query_planner itself to create the child PlannerInfo's by the original parent PlannerInfo. It creates them *after* it has generated enough state in the latter, such as RelOptInfos for various relations, EquivalenceClasses after processing the join tree, and *before* generating the scan and join paths. Scan paths are generated normally, except that Paths of target children are not "appended". Join paths are generated separately for each child target relation, that is, by replacing the parent relation references in the original join tree with the child relation references. Joinrels (RelOptInfos thereof) corresponding to various child target relation are saved in the parent PlannerInfo for further processing. inheritance_planner now calls query_planner just once at the beginning with the original unmodified query, which takes care of generating top-level join rels for the child queries as described above. For each child join rel, it then calls grouping_planner using previously created child PlannerInfo to finish up the paths such that they produce query's top-level target list expanded according to a given child relation's descriptor. grouping_planner()'s interface is modified so that we can pass it what's called a 'planned_rel', a RelOptInfo that already contains the necessary paths needed to produce its output. This removes some existing code in inheritance_planner that dealt with any subquery RTEs in the query. The rationale of that code was that the subquery RTEs may change during each iteration of planning (that is, for different children), so different iterations better use different copies of those RTEs. That was handled by making fresh copies of those RTEs for each iteration of planning which were appended to the range table, accompanied by modifying all expressions and auxiliary structures that referenced the original subquery RTEs to instead reference the copies (that is, change the varnos). This copying meant we would end up adding S * N new entries to the original range table by the time we got to the last unpruned child, where S is the number of subquery RTEs in the original query and N the number of unpruned children. Since with the new code we perform planning just once, I think we don't need this special handling. Actually, there is a regression test output change due to no longer having copies of subquery RTEs (see the diff of partition_join.out file.) Also we no longer need the hack in relation_excluded_by_constraints() involving setting PlannerInfo.inhTargetKind to enable constraint exclusion for target child relation, which is no longer needed. Constraint exclusion now runs during query_planner step described above. Regression test output change in partition_join.out is due to the fact that we no longer create duplicates of subquery RTEs in child range tables. --- doc/src/sgml/ddl.sgml | 17 +- src/backend/optimizer/path/allpaths.c | 452 +++++++++++++++++++++++++-- src/backend/optimizer/path/equivclass.c | 5 +- src/backend/optimizer/plan/planmain.c | 41 ++- src/backend/optimizer/plan/planner.c | 426 +++++++------------------ src/backend/optimizer/util/appendinfo.c | 151 ++++++++- src/backend/optimizer/util/inherit.c | 117 +++++++ src/include/nodes/pathnodes.h | 37 ++- src/include/optimizer/inherit.h | 1 + src/include/optimizer/paths.h | 3 + src/test/regress/expected/partition_join.out | 4 +- 11 files changed, 882 insertions(+), 372 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 8314fce78f..1da28357ef 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -4439,24 +4439,11 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <note> <para> - Currently, pruning of partitions during the planning of an - <command>UPDATE</command> or <command>DELETE</command> command is - implemented using the constraint exclusion method (however, it is - controlled by the <literal>enable_partition_pruning</literal> rather than - <literal>constraint_exclusion</literal>) &mdash; see the following section - for details and caveats that apply. - </para> - - <para> Execution-time partition pruning currently only occurs for the <literal>Append</literal> and <literal>MergeAppend</literal> node types. It is not yet implemented for the <literal>ModifyTable</literal> node - type. - </para> - - <para> - Both of these behaviors are likely to be changed in a future release - of <productname>PostgreSQL</productname>. + type, but that is likely to be changed in a future release of + <productname>PostgreSQL</productname>. </para> </note> </sect2> diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 72184b0cf9..11fc64ff5d 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -39,6 +39,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" @@ -92,8 +93,12 @@ static void set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); +static void set_inherited_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte); static void set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); +static void set_inherited_target_rel_pathlists(PlannerInfo *root, + RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, @@ -122,6 +127,8 @@ static void set_result_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); +static RelOptInfo *inheritance_make_rel_from_joinlist(PlannerInfo *root, + List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, pushdown_safety_info *safetyInfo); static bool recurse_pushdown_safe(Node *setOp, Query *topquery, @@ -155,27 +162,6 @@ make_one_rel(PlannerInfo *root, List *joinlist) Index rti; double total_pages; - /* - * Construct the all_baserels Relids set. - */ - root->all_baserels = NULL; - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - - /* there may be empty slots corresponding to non-baserel RTEs */ - if (brel == NULL) - continue; - - Assert(brel->relid == rti); /* sanity check on array */ - - /* ignore RTEs that are "other rels" */ - if (brel->reloptkind != RELOPT_BASEREL) - continue; - - root->all_baserels = bms_add_member(root->all_baserels, brel->relid); - } - /* Mark base rels as to whether we care about fast-start plans */ set_base_rel_consider_startup(root); @@ -223,13 +209,35 @@ make_one_rel(PlannerInfo *root, List *joinlist) /* * Generate access paths for the entire join tree. + * + * For UPDATE/DELETE on an inheritance parent, join paths should be + * generated for each child result rel separately. */ - rel = make_rel_from_joinlist(root, joinlist); + if (root->parse->resultRelation > 0 && + root->simple_rte_array[root->parse->resultRelation]->inh) + { + /* + * RelOptInfo corresponding to the query's original target relation + * is returned. Join paths (if any) are attached to child joinrels, + * not this rel. Also, this rel's sole path (ModifyTable) will be set + * by inheritance_planner later, so we can't check its paths yet. + */ + rel = inheritance_make_rel_from_joinlist(root, joinlist); + } + else + { + rel = make_rel_from_joinlist(root, joinlist); - /* - * The result should join all and only the query's base rels. - */ - Assert(bms_equal(rel->relids, root->all_baserels)); + /* + * The result should join all and only the query's base rels. + */ + Assert(bms_equal(rel->relids, root->all_baserels)); + + /* Check that we got at least one usable path */ + if (!rel || !rel->cheapest_total_path || + rel->cheapest_total_path->param_info != NULL) + elog(ERROR, "failed to construct the join relation"); + } return rel; } @@ -379,8 +387,15 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_size(root, rel, rti, rte); + /* + * If it's a target relation, set the sizes of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (rti == root->parse->resultRelation) + set_inherited_target_rel_sizes(root, rel, rti, rte); + else + set_append_rel_size(root, rel, rti, rte); } else { @@ -469,14 +484,26 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { + bool inherited_update = false; + if (IS_DUMMY_REL(rel)) { /* We already proved the relation empty, so nothing more to do */ } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_pathlist(root, rel, rti, rte); + /* + * If it's a target relation, set the pathlists of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (rti == root->parse->resultRelation) + { + inherited_update = true; + set_inherited_target_rel_pathlists(root, rel, rti, rte); + } + else + set_append_rel_pathlist(root, rel, rti, rte); } else { @@ -530,6 +557,19 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, } /* + * If the relation is an inheritance parent and the query's result + * relation, no paths have been generated for it yet, so skip the following + * steps. + */ + if (inherited_update) + { +#ifdef OPTIMIZER_DEBUG + debug_print_rel(root, rel); +#endif + return; + } + + /* * Allow a plugin to editorialize on the set of Paths for this base * relation. It could add new paths (such as CustomPaths) by calling * add_path(), or add_partial_path() if parallel aware. It could also @@ -924,6 +964,206 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) } /* + * set_inherited_target_rel_sizes + * Set size estimates for the child target relations + * + * The passed-in rel represents the target relation of the query that is + * known to have inheritance children. This is very much like + * set_append_rel_size, except it doesn't set the size estimates for the + * passed-in rel itself, because we don't need to "append" the children + * in this case. + */ +static void +set_inherited_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + bool has_live_children = false; + ListCell *l; + Relids live_children = NULL; + bool did_pruning = false; + + /* Guard against stack overflow due to overly deep inheritance tree. */ + check_stack_depth(); + + Assert(IS_SIMPLE_REL(rel)); + + /* + * If the partitioned relation has any baserestrictinfo quals then we + * attempt to use these quals to prune away partitions that cannot + * possibly contain any tuples matching these quals. In this case we'll + * store the relids of all partitions which could possibly contain a + * matching tuple, and skip anything else in the loop below. + */ + if (enable_partition_pruning && + rte->relkind == RELKIND_PARTITIONED_TABLE && + rel->baserestrictinfo != NIL) + { + live_children = prune_append_rel_partitions(rel); + did_pruning = true; + } + + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + PlannerInfo *subroot; + ListCell *lc; + List *translated_exprs, + *child_target_exprs; + bool childpruned; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + + /* + * The child rel's RelOptInfo was already created during + * add_base_rels_to_query. + */ + childrel = find_base_rel(root, childRTindex); + Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); + + childpruned = (did_pruning && + !bms_is_member(appinfo->child_relid, live_children)); + + /* + * Unless the child is pruned, 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 baserestrictinfo quals. + */ + if (childpruned || + !apply_child_basequals(root, rel, childrel, childRTE, appinfo) || + relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* This child need not be scanned; skip it. */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Add missing Vars to child's reltarget. + * + * create_inherited_target_child_root() would've added only those that + * are needed to be present in the top-level tlist (or ones that + * preprocess_targetlist thinks are needed to be in the tlist.) We + * may need other attributes such as those contained in WHERE clauses, + * which are already computed for the parent during + * deconstruct_jointree processing of the original query (child's + * query never goes through deconstruct_jointree.) + */ + translated_exprs = (List *) + adjust_appendrel_attrs(root, + (Node *) rel->reltarget->exprs, + 1, &appinfo); + child_target_exprs = childrel->reltarget->exprs; + foreach(lc, translated_exprs) + { + Expr *expr = lfirst(lc); + + if (!list_member(child_target_exprs, expr)) + child_target_exprs = lappend(child_target_exprs, expr); + } + + childrel->has_eclass_joins = rel->has_eclass_joins; + + subroot = root->inh_target_child_roots[childRTindex]; + + /* Translate join quals. */ + childrel->joininfo = (List *) + adjust_appendrel_attrs(subroot, + (Node *) rel->joininfo, + 1, &appinfo); + + /* + * Compute the child's size using possibly modified subroot. + */ + set_rel_size(subroot, childrel, childRTindex, childRTE); + + /* + * If the child is a partitioned table it may have been marked as a + * dummy rel from having all its partitions pruned. + */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* We have at least one live child. */ + has_live_children = true; + + Assert(childrel->rows > 0); + } + + if (has_live_children) + { + /* + * Set a non-zero value here to cope with the caller's requirement + * that non-dummy relations are actually not empty. We don't try to + * be accurate here, because we're not going to create a path that + * combines the children outputs. + */ + rel->rows = 1; + } + else + { + /* + * All children were excluded by constraints, so mark the relation + * as dummy. We must do this in this phase so that the rel's + * dummy-ness is visible when we generate paths for other rels. + */ + set_dummy_rel_pathlist(rel); + } +} + +/* + * set_inherited_target_rel_pathlists + * Build access paths for the child target relations + * + * Similar to set_append_rel_pathlist, except that we build paths of the + * children, but don't build an Append path. + */ +static void +set_inherited_target_rel_pathlists(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + ListCell *l; + + /* Generate access paths for each of the children of passed-in rel */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + PlannerInfo *subroot; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + /* Re-locate the child RTE and RelOptInfo */ + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + childrel = root->simple_rel_array[childRTindex]; + subroot = root->inh_target_child_roots[childRTindex]; + /* Transfer the value from main root to subroot. */ + subroot->total_table_pages = root->total_table_pages; + + /* + * Compute the child's access paths. + */ + set_rel_pathlist(subroot, childrel, childRTindex, childRTE); + } +} + +/* * set_append_rel_size * Set size estimates for a simple "append relation" * @@ -2551,6 +2791,158 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows) } /* + * inheritance_make_rel_from_joinlist + * Perform join planning for all non-dummy leaf inheritance children + * in their role as an UPDATE/DELETE query's target relation + * + * If a child relation is a partitioned table, its children are processed in + * turn by recursively calling this function. + */ +static RelOptInfo * +inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) +{ + Index resultRelation = root->parse->resultRelation; + RelOptInfo *resultrel; + ListCell *lc; +#ifdef USE_ASSERT_CHECKING + Relids all_baserels; +#endif + + /* For UPDATE/DELETE queries, the top parent can only ever be a table. */ + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + Assert(planner_rt_fetch(resultRelation, root)->rtekind == RTE_RELATION); + resultrel = find_base_rel(root, resultRelation); + + /* Nothing to do. */ + if (IS_DUMMY_REL(resultrel)) + return resultrel; + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + PlannerInfo *subroot; + RelOptInfo *childrel; + RelOptInfo *childjoinrel; + List *translated_joinlist; + + if (appinfo->parent_relid != resultRelation) + continue; + + childrel = find_base_rel(root, appinfo->child_relid); + + /* Ignore excluded/pruned children. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* Add this child. */ + root->inh_target_child_rels = lappend_int(root->inh_target_child_rels, + appinfo->child_relid); + + /* Perform join planning with child subroot. */ + subroot = root->inh_target_child_roots[appinfo->child_relid]; + Assert(subroot->parse->resultRelation > 0); + + /* + * Modify joinlist such that relations joined to the top parent rel + * appear to be joined to the child rel instead. Do the same for + * any SpecialJoinInfo structs. + */ + translated_joinlist = (List *) + adjust_appendrel_attrs(subroot, + (Node *) joinlist, + 1, &appinfo); + subroot->join_info_list = (List *) + adjust_appendrel_attrs(subroot, + (Node *) root->join_info_list, + 1, &appinfo); + + /* + * Sub-partitioned tables have to be processed recursively using the + * translated subroot as the parent, because AppendRelInfos link + * sub-partitions to their immediate parents, not the root partitioned + * table. + */ + if (childrel->part_scheme != NULL) + { + /* + * inheritance_make_rel_from_joinlist() return the target relation + * RelOptInfo. + */ + childrel = + inheritance_make_rel_from_joinlist(subroot, + translated_joinlist); + + /* + * Add this child relation as a placeholder in the parent root's + * inh_target_child_path_rels so that inheritance_planner sees + * same number of entries in it as inh_target_child_rels. + */ + root->inh_target_child_path_rels = + lappend(root->inh_target_child_path_rels, childrel); + + /* + * Also propagate this child's own children into the parent's + * list. + */ + if (subroot->inh_target_child_rels != NIL) + { + root->inh_target_child_rels = + list_concat(root->inh_target_child_rels, + subroot->inh_target_child_rels); + root->inh_target_child_path_rels = + list_concat(root->inh_target_child_path_rels, + subroot->inh_target_child_path_rels); + } + continue; + } + + /* + * Since we added the child rel directly into the join tree, we must + * modify it to be a "base" rel instead of an "other" rel, which the + * join planning code expects the relations being joined to be. + */ + childrel->reloptkind = RELOPT_BASEREL; + + Assert(subroot->join_rel_list == NIL); + Assert(subroot->join_rel_hash == NULL); + + /* Perform join planning and save the resulting RelOptInfo. */ + childjoinrel = make_rel_from_joinlist(subroot, translated_joinlist); + + /* Check that we got at least one usable path */ + if (!childjoinrel || !childjoinrel->cheapest_total_path || + childjoinrel->cheapest_total_path->param_info != NULL) + elog(ERROR, "failed to construct the child join relation"); + + /* + * Remember the paths of child target rel. inheritance_planner will + * perform the remaining steps of planning for each child relation + * separately. Specifically, it will call grouping_planner on every + * RelOptInfo contained in the inh_target_child_rels list, each of + * which represents the source of tuples to be modified for a given + * target child rel. + */ + root->inh_target_child_path_rels = + lappend(root->inh_target_child_path_rels, childjoinrel); +#ifdef USE_ASSERT_CHECKING + /* + * The following implements essentially the same Assert as in + * make_one_rel, our caller. + */ + all_baserels = bms_copy(root->all_baserels); + all_baserels = bms_del_member(all_baserels, + root->parse->resultRelation); + all_baserels = bms_add_member(all_baserels, + subroot->parse->resultRelation); + Assert(bms_equal(childjoinrel->relids, all_baserels)); +#endif + } + + return resultrel; +} + +/* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. * diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 23792508b7..8dcc20275e 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -32,9 +32,6 @@ #include "utils/lsyscache.h" -static EquivalenceMember *add_eq_member(EquivalenceClass *ec, - Expr *expr, Relids relids, Relids nullable_relids, - bool is_child, Oid datatype); static void generate_base_implied_equalities_const(PlannerInfo *root, EquivalenceClass *ec); static void generate_base_implied_equalities_no_const(PlannerInfo *root, @@ -540,7 +537,7 @@ canonicalize_ec_expression(Expr *expr, Oid req_type, Oid req_collation) /* * add_eq_member - build a new EquivalenceMember and add it to an EC */ -static EquivalenceMember * +EquivalenceMember * add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids, Relids nullable_relids, bool is_child, Oid datatype) { diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 3cedd01c98..c7f2323852 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -20,6 +20,7 @@ */ #include "postgres.h" +#include "nodes/pathnodes.h" #include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/inherit.h" @@ -29,6 +30,7 @@ #include "optimizer/paths.h" #include "optimizer/placeholder.h" #include "optimizer/planmain.h" +#include "optimizer/prep.h" /* @@ -60,6 +62,7 @@ query_planner(PlannerInfo *root, List *tlist, Query *parse = root->parse; List *joinlist; RelOptInfo *final_rel; + Index rti; /* * Init planner lists to empty. @@ -260,14 +263,42 @@ query_planner(PlannerInfo *root, List *tlist, extract_restriction_or_clauses(root); /* + * Construct the all_baserels Relids set. + */ + root->all_baserels = NULL; + for (rti = 1; rti < root->simple_rel_array_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + Assert(brel->relid == rti); /* sanity check on array */ + + /* ignore RTEs that are "other rels" */ + if (brel->reloptkind != RELOPT_BASEREL) + continue; + + root->all_baserels = bms_add_member(root->all_baserels, brel->relid); + } + + /* + * Add child subroots needed during planning for individual child targets. + */ + if (parse->resultRelation && + root->simple_rte_array[parse->resultRelation]->inh) + { + root->inh_target_child_roots = (PlannerInfo **) + palloc0(root->simple_rel_array_size * + sizeof(PlannerInfo *)); + add_inherited_target_child_roots(root); + } + + /* * Ready to do the primary planning. */ final_rel = make_one_rel(root, joinlist); - /* Check that we got at least one usable path */ - if (!final_rel || !final_rel->cheapest_total_path || - final_rel->cheapest_total_path->param_info != NULL) - elog(ERROR, "failed to construct the join relation"); - return final_rel; } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index ddb86bd0c3..fdf17d1b35 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -131,7 +131,7 @@ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static void inheritance_planner(PlannerInfo *root); static void grouping_planner(PlannerInfo *root, bool inheritance_update, - double tuple_fraction); + RelOptInfo *planned_rel, double tuple_fraction); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, int *tleref_to_colnum_map); @@ -997,7 +997,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh) inheritance_planner(root); else - grouping_planner(root, false, tuple_fraction); + grouping_planner(root, false, NULL, tuple_fraction); /* * Capture the set of outer-level param IDs we have access to, for use in @@ -1173,13 +1173,39 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr) * inheritance set. * * We have to handle this case differently from cases where a source relation - * is an inheritance set. Source inheritance is expanded at the bottom of the - * plan tree (see allpaths.c), but target inheritance has to be expanded at - * the top. The reason is that for UPDATE, each target relation needs a - * different targetlist matching its own column set. Fortunately, + * is an inheritance set, where outputs of all the children are combined and + * combined output consumed using the source relation's (parent's) column set. + * For UPDATE, each target relation, where the query's output will go, needs + * a different targetlist matching its own column set. So, we will need to + * translate the query such that it produces the desired output required by + * each of the child target relations and plan each separately. Fortunately, * the UPDATE/DELETE target can never be the nullable side of an outer join, * so it's OK to generate the plan this way. * + * Planning steps that need to be performed separately for each child and + * any preliminary processing needed for that are handled by the following + * sub-routines of query_planner: + * + * add_inherited_target_child_roots - this creates copies of PlannerInfo for + * each child after query_planner has finished processing the join tree, with + * translated copies of parsetree, eq_classes, etc. + * + * set_inherited_target_rel_sizes - this sets size estimates for child + * relations + * + * set_inherited_target_rel_pathlists - this creates Paths for scanning + * individual child relations + * + * inheritance_make_rel_from_joinlist - this translates the jointree, + * replacing the target relation in the original jointree (the root parent) + * by individual child target relations and performs join planning on the + * resulting join tree, saving the RelOptInfos of resulting join relations + * into the top-level PlannerInfo + * + * Finally, here we apply grouping_planner to each child join relation + * produced by set_inherited_target_rel_pathlists to so that its path produces + * the desired targetlist. + * * Returns nothing; the useful output is in the Paths we attach to * the (UPPERREL_FINAL, NULL) upperrel stored in *root. * @@ -1191,14 +1217,8 @@ inheritance_planner(PlannerInfo *root) { Query *parse = root->parse; int top_parentRTindex = parse->resultRelation; - Bitmapset *subqueryRTindexes; - Bitmapset *modifiableARIindexes; int nominalRelation = -1; Index rootRelation = 0; - List *final_rtable = NIL; - int save_rel_array_size = 0; - RelOptInfo **save_rel_array = NULL; - AppendRelInfo **save_append_rel_array = NULL; List *subpaths = NIL; List *subroots = NIL; List *resultRelations = NIL; @@ -1206,70 +1226,47 @@ inheritance_planner(PlannerInfo *root) List *returningLists = NIL; List *rowMarks; RelOptInfo *final_rel; - ListCell *lc; - Index rti; + ListCell *lc1, + *lc2; RangeTblEntry *parent_rte; - PlannerInfo *parent_root; - Query *parent_parse; - Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); - PlannerInfo **parent_roots = NULL; + List *tlist; + standard_qp_extra qp_extra; + RelOptInfo *planned_rel; + /* Inheritance is never used for insert. */ Assert(parse->commandType != CMD_INSERT); /* - * We generate a modified instance of the original Query for each target - * relation, plan that, and put all the plans into a list that will be - * controlled by a single ModifyTable node. All the instances share the - * same rangetable, but each instance must have its own set of subquery - * RTEs within the finished rangetable because (1) they are likely to get - * scribbled on during planning, and (2) it's not inconceivable that - * subqueries could get planned differently in different cases. We need - * not create duplicate copies of other RTE kinds, in particular not the - * target relations, because they don't have either of those issues. Not - * having to duplicate the target relations is important because doing so - * (1) would result in a rangetable of length O(N^2) for N targets, with - * at least O(N^3) work expended here; and (2) would greatly complicate - * management of the rowMarks list. + * Let query_planner generate the access paths for the query for each + * target child relation. * - * To begin with, generate a bitmapset of the relids of the subquery RTEs. + * First, save the unexpanded version of the query's targetlist. + * create_inherited_target_child_root will use it as base when expanding + * it for a given child relation as the query's target relation instead + * of the parent. */ - subqueryRTindexes = NULL; - rti = 1; - foreach(lc, parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - if (rte->rtekind == RTE_SUBQUERY) - subqueryRTindexes = bms_add_member(subqueryRTindexes, rti); - rti++; - } + root->unexpanded_tlist = list_copy(root->parse->targetList); + tlist = preprocess_targetlist(root); + root->processed_tlist = tlist; + qp_extra.tlist = tlist; + qp_extra.activeWindows = NIL; + qp_extra.groupClause = NIL; + planned_rel = query_planner(root, tlist, standard_qp_callback, &qp_extra); /* - * Next, we want to identify which AppendRelInfo items contain references - * to any of the aforesaid subquery RTEs. These items will need to be - * copied and modified to adjust their subquery references; whereas the - * other ones need not be touched. It's worth being tense over this - * because we can usually avoid processing most of the AppendRelInfo - * items, thereby saving O(N^2) space and time when the target is a large - * inheritance tree. We can identify AppendRelInfo items by their - * child_relid, since that should be unique within the list. + * If it turned out during query planning that all the children are dummy + * (pruned or excluded by constraints), no need to do the steps below. + * Let grouping_planner finish up the final path. */ - modifiableARIindexes = NULL; - if (subqueryRTindexes != NULL) + if (IS_DUMMY_REL(planned_rel)) { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - - if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) || - bms_is_member(appinfo->child_relid, subqueryRTindexes) || - bms_overlap(pull_varnos((Node *) appinfo->translated_vars), - subqueryRTindexes)) - modifiableARIindexes = bms_add_member(modifiableARIindexes, - appinfo->child_relid); - } + grouping_planner(root, false, planned_rel, 0.0); + return; } + Assert(planned_rel->relid == top_parentRTindex); + Assert(planned_rel->reloptkind == RELOPT_BASEREL); + /* * If the parent RTE is a partitioned table, we should use that as the * nominal target relation, because the RTEs added for partitioned tables @@ -1277,7 +1274,7 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); + parent_rte = planner_rt_fetch(top_parentRTindex, root); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1285,66 +1282,29 @@ inheritance_planner(PlannerInfo *root) } /* - * The PlannerInfo for each child is obtained by translating the relevant - * members of the PlannerInfo for its immediate parent, which we find - * using the parent_relid in its AppendRelInfo. We save the PlannerInfo - * for each parent in an array indexed by relid for fast retrieval. Since - * the maximum number of parents is limited by the number of RTEs in the - * query, we use that number to allocate the array. An extra entry is - * needed since relids start from 1. + * Get on with finalizing the path for each child target relation by + * calling grouping_planner on its joinrel. */ - parent_roots = (PlannerInfo **) palloc0((list_length(parse->rtable) + 1) * - sizeof(PlannerInfo *)); - parent_roots[top_parentRTindex] = root; - - /* - * And now we can get on with generating a plan for each child table. - */ - foreach(lc, root->append_rel_list) + forboth(lc1, root->inh_target_child_rels, + lc2, root->inh_target_child_path_rels) { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - PlannerInfo *subroot; + Index childRTindex = lfirst_int(lc1); + RelOptInfo *childjoinrel = lfirst(lc2); + PlannerInfo *subroot = root->inh_target_child_roots[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; RangeTblEntry *child_rte; RelOptInfo *sub_final_rel; Path *subpath; - /* append_rel_list contains all append rels; ignore others */ - if (!bms_is_member(appinfo->parent_relid, parent_relids)) - continue; - - /* - * expand_inherited_rtentry() always processes a parent before any of - * that parent's children, so the parent_root for this relation should - * already be available. - */ - parent_root = parent_roots[appinfo->parent_relid]; - Assert(parent_root != NULL); - parent_parse = parent_root->parse; - - /* - * We need a working copy of the PlannerInfo so that we can control - * propagation of information back to the main copy. - */ - subroot = makeNode(PlannerInfo); - memcpy(subroot, parent_root, sizeof(PlannerInfo)); - - /* - * Generate modified query with this rel as target. We first apply - * adjust_appendrel_attrs, which copies the Query and changes - * references to the parent RTE to refer to the current child RTE, - * then fool around with subquery RTEs. - */ - subroot->parse = (Query *) - adjust_appendrel_attrs(parent_root, - (Node *) parent_parse, - 1, &appinfo); + Assert(subroot != NULL); + Assert(subroot->parse->resultRelation == childRTindex); /* * If there are securityQuals attached to the parent, move them to the * child rel (they've already been transformed properly for that). */ - parent_rte = rt_fetch(appinfo->parent_relid, subroot->parse->rtable); - child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable); + parent_rte = planner_rt_fetch(appinfo->parent_relid, subroot); + child_rte = planner_rt_fetch(appinfo->child_relid, subroot); child_rte->securityQuals = parent_rte->securityQuals; parent_rte->securityQuals = NIL; @@ -1356,24 +1316,12 @@ inheritance_planner(PlannerInfo *root) (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED; /* - * If this child is further partitioned, remember it as a parent. - * Since a partitioned table does not have any data, we don't need to - * create a plan for it, and we can stop processing it here. We do, - * however, need to remember its modified PlannerInfo for use when - * processing its children, since we'll update their varnos based on - * the delta from immediate parent to child, not from top to child. - * - * Note: a very non-obvious point is that we have not yet added - * duplicate subquery RTEs to the subroot's rtable. We mustn't, - * because then its children would have two sets of duplicates, - * confusing matters. + * Ignore a partitioned child. Instead, the paths of its children will + * be added to subpaths. */ - if (child_rte->inh) + if (childjoinrel->part_scheme) { - Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE); - parent_relids = bms_add_member(parent_relids, appinfo->child_relid); - parent_roots[appinfo->child_relid] = subroot; - + Assert(child_rte->inh); continue; } @@ -1401,111 +1349,8 @@ inheritance_planner(PlannerInfo *root) if (nominalRelation < 0) nominalRelation = appinfo->child_relid; - /* - * The rowMarks list might contain references to subquery RTEs, so - * make a copy that we can apply ChangeVarNodes to. (Fortunately, the - * executor doesn't need to see the modified copies --- we can just - * pass it the original rowMarks list.) - */ - subroot->rowMarks = copyObject(parent_root->rowMarks); - - /* - * The append_rel_list likewise might contain references to subquery - * RTEs (if any subqueries were flattenable UNION ALLs). So prepare - * to apply ChangeVarNodes to that, too. As explained above, we only - * want to copy items that actually contain such references; the rest - * can just get linked into the subroot's append_rel_list. - * - * If we know there are no such references, we can just use the outer - * append_rel_list unmodified. - */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - subroot->append_rel_list = NIL; - foreach(lc2, parent_root->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, modifiableARIindexes)) - appinfo2 = copyObject(appinfo2); - - subroot->append_rel_list = lappend(subroot->append_rel_list, - appinfo2); - } - } - - /* - * Add placeholders to the child Query's rangetable list to fill the - * RT indexes already reserved for subqueries in previous children. - * These won't be referenced, so there's no need to make them very - * valid-looking. - */ - while (list_length(subroot->parse->rtable) < list_length(final_rtable)) - subroot->parse->rtable = lappend(subroot->parse->rtable, - makeNode(RangeTblEntry)); - - /* - * If this isn't the first child Query, generate duplicates of all - * subquery RTEs, and adjust Var numbering to reference the - * duplicates. To simplify the loop logic, we scan the original rtable - * not the copy just made by adjust_appendrel_attrs; that should be OK - * since subquery RTEs couldn't contain any references to the target - * rel. - */ - if (final_rtable != NIL && subqueryRTindexes != NULL) - { - ListCell *lr; - - rti = 1; - foreach(lr, parent_parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lr); - - if (bms_is_member(rti, subqueryRTindexes)) - { - Index newrti; - - /* - * The RTE can't contain any references to its own RT - * index, except in its securityQuals, so we can save a - * few cycles by applying ChangeVarNodes to the rest of - * the rangetable before we append the RTE to it. - */ - newrti = list_length(subroot->parse->rtable) + 1; - ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); - ChangeVarNodes((Node *) subroot->rowMarks, rti, newrti, 0); - /* Skip processing unchanging parts of append_rel_list */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - foreach(lc2, subroot->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, - modifiableARIindexes)) - ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); - } - } - rte = copyObject(rte); - ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); - subroot->parse->rtable = lappend(subroot->parse->rtable, - rte); - } - rti++; - } - } - - /* There shouldn't be any OJ info to translate, as yet */ - Assert(subroot->join_info_list == NIL); - /* and we haven't created PlaceHolderInfos, either */ - Assert(subroot->placeholder_list == NIL); - - /* Generate Path(s) for accessing this result relation */ - grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ ); + /* Apply the top-level targetlist to childjoinrel's path. */ + grouping_planner(subroot, true, childjoinrel, 0.0); /* * Select cheapest path in case there's more than one. We always run @@ -1517,45 +1362,10 @@ inheritance_planner(PlannerInfo *root) subpath = sub_final_rel->cheapest_total_path; /* - * If this child rel was excluded by constraint exclusion, exclude it - * from the result plan. + * child rel cannot be empty, or inheritance_make_rel_from_joinlist + * wouldn't have put it in the list to begin with. */ - if (IS_DUMMY_PATH(subpath)) - continue; - - /* - * If this is the first non-excluded child, its post-planning rtable - * becomes the initial contents of final_rtable; otherwise, append - * just its modified subquery RTEs to final_rtable. - */ - if (final_rtable == NIL) - final_rtable = subroot->parse->rtable; - else - final_rtable = list_concat(final_rtable, - list_copy_tail(subroot->parse->rtable, - list_length(final_rtable))); - - /* - * We need to collect all the RelOptInfos from all child plans into - * the main PlannerInfo, since setrefs.c will need them. We use the - * last child's simple_rel_array (previous ones are too short), so we - * have to propagate forward the RelOptInfos that were already built - * in previous children. - */ - Assert(subroot->simple_rel_array_size >= save_rel_array_size); - for (rti = 1; rti < save_rel_array_size; rti++) - { - RelOptInfo *brel = save_rel_array[rti]; - - if (brel) - subroot->simple_rel_array[rti] = brel; - } - save_rel_array_size = subroot->simple_rel_array_size; - save_rel_array = subroot->simple_rel_array; - save_append_rel_array = subroot->append_rel_array; - - /* Make sure any initplans from this rel get into the outer list */ - root->init_plans = subroot->init_plans; + Assert(!IS_DUMMY_PATH(subpath)); /* Build list of sub-paths */ subpaths = lappend(subpaths, subpath); @@ -1587,36 +1397,6 @@ inheritance_planner(PlannerInfo *root) */ /* - * If we managed to exclude every child rel, return a dummy plan; it - * doesn't even need a ModifyTable node. - */ - if (subpaths == NIL) - { - set_dummy_rel_pathlist(final_rel); - return; - } - - /* - * Put back the final adjusted rtable into the master copy of the Query. - * (We mustn't do this if we found no non-excluded children.) - */ - parse->rtable = final_rtable; - root->simple_rel_array_size = save_rel_array_size; - root->simple_rel_array = save_rel_array; - root->append_rel_array = save_append_rel_array; - - /* Must reconstruct master's simple_rte_array, too */ - root->simple_rte_array = (RangeTblEntry **) - palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *)); - rti = 1; - foreach(lc, final_rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - root->simple_rte_array[rti++] = rte; - } - - /* * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will * have dealt with fetching non-locked marked rows, else we need to have * ModifyTable do that. @@ -1656,6 +1436,12 @@ inheritance_planner(PlannerInfo *root) * (inheritance_planner will create a single ModifyTable node covering all the * target tables.) * + * If non-NULL, planned_rel is a RelOptInfo containing paths for the query's + * top-level joinrel, which the caller produced by itself. In that case, this + * function only needs to adjust the targetlist of its cheapest_total_path. + * The only caller that may pass such a RelOptInfo currently is + * inheritance_planner. + * * tuple_fraction is the fraction of tuples we expect will be retrieved. * tuple_fraction is interpreted as follows: * 0: expect all tuples to be retrieved (normal case) @@ -1674,6 +1460,7 @@ inheritance_planner(PlannerInfo *root) */ static void grouping_planner(PlannerInfo *root, bool inheritance_update, + RelOptInfo *planned_rel, double tuple_fraction) { Query *parse = root->parse; @@ -1686,7 +1473,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, List *final_targets; List *final_targets_contain_srfs; bool final_target_parallel_safe; - RelOptInfo *current_rel; + RelOptInfo *current_rel = planned_rel; RelOptInfo *final_rel; ListCell *lc; @@ -1726,6 +1513,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * special work for recursive unions is the responsibility of * plan_set_operations. */ + Assert(current_rel == NULL); current_rel = plan_set_operations(root); /* @@ -1815,17 +1603,26 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, parse->groupClause = preprocess_groupclause(root, NIL); } - /* Preprocess targetlist */ - tlist = preprocess_targetlist(root); - /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation so - * that we can transfer its decoration (resnames etc) to the topmost - * tlist of the finished Plan. + * Preprocess targetlist, if needed. If the caller has already done + * query planning, root->processed_tlist already contains the desired + * targetlist. */ - root->processed_tlist = tlist; + if (planned_rel == NULL) + { + tlist = preprocess_targetlist(root); + + /* + * We are now done hacking up the query's targetlist. Most of the + * remaining planning work will be done with the PathTarget + * representation of tlists, but save aside the full representation + * so that we can transfer its decoration (resnames etc) to the + * topmost tlist of the finished Plan. + */ + root->processed_tlist = tlist; + } + else + tlist = root->processed_tlist; /* * Collect statistics about aggregates for estimating costs, and mark @@ -1905,8 +1702,9 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * We also generate (in standard_qp_callback) pathkey representations * of the query's sort clause, distinct clause, etc. */ - current_rel = query_planner(root, tlist, - standard_qp_callback, &qp_extra); + if (current_rel == NULL) + current_rel = query_planner(root, tlist, + standard_qp_callback, &qp_extra); /* * Convert the query's result tlist into PathTarget format. diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index ca6622ece9..12727ce97f 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -18,6 +18,8 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" #include "parser/parsetree.h" #include "utils/lsyscache.h" #include "utils/rel.h" @@ -39,6 +41,8 @@ static Node *adjust_appendrel_attrs_mutator(Node *node, adjust_appendrel_attrs_context *context); static List *adjust_inherited_tlist(List *tlist, AppendRelInfo *context); +static EquivalenceClass * adjust_inherited_eq_class(PlannerInfo *root, + EquivalenceClass *new_ec, AppendRelInfo *appinfo); /* @@ -394,8 +398,71 @@ adjust_appendrel_attrs_mutator(Node *node, context->appinfos); return (Node *) phv; } + + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + + if (IsA(node, EquivalenceClass)) + { + EquivalenceClass *ec = (EquivalenceClass *) node; + EquivalenceClass *new_ec = makeNode(EquivalenceClass); + + /* + * First memcpy the existing EC to the new EC, which creates shallow + * copies of all the members and then make copies of members that + * we might change below. + * + * XXX comment in _copyPathKey says it's OK to recycle EC + * pointers, but as long as we do the whole planning for a + * given child using a given root, copying ECs like this + * shouldn't be a problem. Maybe, the following code could + * be in _copyEquivalenceClass()? + */ + memcpy(new_ec, ec, sizeof(EquivalenceClass)); + new_ec->ec_opfamilies = list_copy(ec->ec_opfamilies); + new_ec->ec_sources = list_copy(ec->ec_sources); + new_ec->ec_derives = list_copy(ec->ec_derives); + new_ec->ec_relids = bms_copy(ec->ec_relids); + new_ec->ec_members = list_copy(ec->ec_members); + + /* + * No point in searching if parent rel not mentioned in eclass; but we + * can't tell that for sure if parent rel is itself a child. + */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + if (bms_is_member(appinfos[cnt]->parent_relid, ec->ec_relids)) + new_ec = adjust_inherited_eq_class(context->root, new_ec, + appinfos[cnt]); + } + + return (Node *) new_ec; + } + /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); @@ -473,6 +540,88 @@ adjust_appendrel_attrs_mutator(Node *node, } /* + * inh_translate_eq_class + * This translates members of EquivalenceClass such that references to + * an inheritance parent relation are replaced by the references to + * a given child relation + * + * This performs in-place updates of the members of the input struct, so the + * callers should've made a copy of the original EC first. + */ +static EquivalenceClass * +adjust_inherited_eq_class(PlannerInfo *root, EquivalenceClass *new_ec, + AppendRelInfo *appinfo) +{ + ListCell *lc; + RelOptInfo *parent_rel = find_base_rel(root, appinfo->parent_relid), + *child_rel = find_base_rel(root, appinfo->child_relid); + + /* + * Check if this EC contains an expression referencing the parent + * relation, translate it to child, and store it in place of + * the original parent expression. + */ + foreach(lc, new_ec->ec_members) + { + EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc); + + if (cur_em->em_is_const) + continue; /* ignore consts here */ + + /* Does it reference parent_rel? */ + if (bms_overlap(cur_em->em_relids, parent_rel->relids)) + { + /* Yes, generate transformed child version */ + Expr *new_expr; + Relids new_relids; + Relids new_nullable_relids; + + new_expr = (Expr *) + adjust_appendrel_attrs(root, + (Node *) cur_em->em_expr, + 1, &appinfo); + + /* + * Transform em_relids to match. Note we do *not* do + * pull_varnos(child_expr) here, as for example the + * transformation might have substituted a constant, but we + * don't want the child member to be marked as constant. + */ + new_relids = bms_difference(cur_em->em_relids, parent_rel->relids); + new_relids = bms_add_members(new_relids, child_rel->relids); + + /* + * And likewise for nullable_relids. Note this code assumes + * parent and child relids are singletons. + */ + new_nullable_relids = cur_em->em_nullable_relids; + if (bms_overlap(new_nullable_relids, parent_rel->relids)) + { + new_nullable_relids = bms_difference(new_nullable_relids, + parent_rel->relids); + new_nullable_relids = bms_add_members(new_nullable_relids, + child_rel->relids); + } + + /* + * The new expression simply replaces the old parent one, and + * em_is_child is set to true so that it's recognized as such + * during child planning. + */ + lfirst(lc) = add_eq_member(new_ec, new_expr, + new_relids, new_nullable_relids, + false, cur_em->em_datatype); + } + } + + /* Now fix up EC's relids set. */ + new_ec->ec_relids = bms_del_members(new_ec->ec_relids, parent_rel->relids); + new_ec->ec_relids = bms_add_members(new_ec->ec_relids, child_rel->relids); + + return new_ec; +} + +/* * adjust_appendrel_attrs_multilevel * Apply Var translations from a toplevel appendrel parent down to a child. * diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index eaf788e578..0ca320abcc 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -21,11 +21,14 @@ #include "miscadmin.h" #include "optimizer/appendinfo.h" #include "optimizer/inherit.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "utils/rel.h" +static PlannerInfo *create_inherited_target_child_root(PlannerInfo *root, + AppendRelInfo *appinfo); static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti); static void expand_partitioned_rtentry(PlannerInfo *root, @@ -492,3 +495,117 @@ translate_col_privs(const Bitmapset *parent_privs, return child_privs; } + +/* + * add_inherited_target_child_roots + * For each child of the query's result relation, this translates the + * original query to match the child and creates a PlannerInfo containing + * the translated query + * + * Child PlannerInfo reuses most of the parent PlannerInfo's fields unchanged, + * except unexpanded_tlist, processed_tlist, and all_baserels, all of which + * are based on the child relation. + */ +void +add_inherited_target_child_roots(PlannerInfo *root) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; + + Assert(root->inh_target_child_roots != NULL); + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + RangeTblEntry *childRTE; + PlannerInfo *subroot; + + if (appinfo->parent_relid != resultRelation) + continue; + + /* + * Create a PlannerInfo for processing this child target relation + * with. + */ + subroot = create_inherited_target_child_root(root, appinfo); + root->inh_target_child_roots[appinfo->child_relid] = subroot; + + /* + * If the child is a partitioned table, recurse to do this for its + * partitions. + */ + childRTE = root->simple_rte_array[appinfo->child_relid]; + if (childRTE->inh) + add_inherited_target_child_roots(subroot); + } +} + +/* + * create_inherited_target_child_root + * Workhorse of add_inherited_target_child_roots + */ +static PlannerInfo * +create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) +{ + PlannerInfo *subroot; + List *tlist; + + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + + /* + * Translate the original query to replace Vars of the parent table + * by the corresponding Vars of the child table and to make child the main + * target relation of the query. + */ + subroot = makeNode(PlannerInfo); + memcpy(subroot, root, sizeof(PlannerInfo)); + + /* + * Restore the original, unexpanded targetlist, that is, the one before + * preprocess_targetlist was run on the original query. We'll run + * preprocess_targetlist after translating the query and the targetlist, + * so that it is expanded according to child's tuple descriptor. + */ + root->parse->targetList = root->unexpanded_tlist; + subroot->parse = (Query *) adjust_appendrel_attrs(root, + (Node *) root->parse, + 1, &appinfo); + + /* + * Translate ECs. This copies the list of ECs, translating the + * relevant members of individual ECs to replace the references to + * the parent RTE by child RTE, including the EC members. + */ + subroot->eq_classes = (List *) + adjust_appendrel_attrs(root, + (Node *) root->eq_classes, + 1, &appinfo); + + /* + * Save the just translated targetlist as unexpanded_tlist in the child's + * subroot, so that this child's own children can use it. Must use copy + * because subroot->parse->targetList will be modified soon. + */ + subroot->unexpanded_tlist = list_copy(subroot->parse->targetList); + + /* + * Apply planner's expansion of targetlist, such as adding various junk + * column, filling placeholder entries for dropped columns, etc., all of + * which occurs with the child's TupleDesc. + */ + tlist = preprocess_targetlist(subroot); + subroot->processed_tlist = tlist; + + /* Add any newly added Vars to the child RelOptInfo. */ + build_base_rel_tlists(subroot, tlist); + + /* + * Adjust all_baserels to replace the original target relation with the + * child target relation. + */ + subroot->all_baserels = adjust_child_relids(subroot->all_baserels, + 1, &appinfo); + + return subroot; +} diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 616ec3b3db..a9606fab15 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -325,7 +325,7 @@ struct PlannerInfo Index qual_security_level; /* minimum security_level for quals */ /* Note: qual_security_level is zero if there are no securityQuals */ - InheritanceKind inhTargetKind; /* indicates if the target relation is an + InheritanceKind inhTargetKind; /* indicates if the target relation is an * inheritance child or partition or a * partitioned table */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ @@ -348,9 +348,44 @@ struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* + * The following fields are set during query planning portion of an + * inherited UPDATE/DELETE operation. + */ + + /* + * This stores the original version of the query's targetlist that's + * not modified by the planner. + */ + List *unexpanded_tlist; + + /* + * Array containing simple_rel_array_size elements, indexed by rangetable + * index (entry 0 is wasted like simple_rel_array). Only elements + * corresponding to individual inheritance child target relations are + * non-NULL. Content of each PlannerInfo is mostly same as the parent + * PlannerInfo, except for few fields such as the parse tree which is + * a translated copy of the parent's parse tree, EC list which contain + * child member expressions, etc. + */ + struct PlannerInfo **inh_target_child_roots; + + /* List of RT indexes of child target relations. */ + List *inh_target_child_rels; + + /* + * RelOptInfos corresponding to each child target rel. For leaf children, + * it's the RelOptInfo representing the output of make_rel_from_joinlist() + * called with the parent rel in the original join tree replaced by a + * given leaf child. For non-leaf children, it's the baserel RelOptInfo + * itself, left as a placeholder. + */ + List *inh_target_child_path_rels; }; + /* * In places where it's known that simple_rte_array[] must have been prepared * already, we just index into it to fetch RTEs. In code that might be diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h index d2418f15cf..4f06cdd322 100644 --- a/src/include/optimizer/inherit.h +++ b/src/include/optimizer/inherit.h @@ -18,5 +18,6 @@ extern void expand_inherited_tables(PlannerInfo *root); +extern void add_inherited_target_child_roots(PlannerInfo *root); #endif /* INHERIT_H */ diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 040335a7c5..dab7b07a98 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -124,6 +124,9 @@ typedef bool (*ec_matches_callback_type) (PlannerInfo *root, extern bool process_equivalence(PlannerInfo *root, RestrictInfo **p_restrictinfo, bool below_outer_join); +extern EquivalenceMember *add_eq_member(EquivalenceClass *ec, + Expr *expr, Relids relids, Relids nullable_relids, + bool is_child, Oid datatype); extern Expr *canonicalize_ec_expression(Expr *expr, Oid req_type, Oid req_collation); extern void reconsider_outer_join_clauses(PlannerInfo *root); diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index bbdc373782..fd85b5ead3 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1780,7 +1780,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_1 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_1 -> Nested Loop Semi Join @@ -1788,7 +1788,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_2 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_2 (28 rows) -- 2.11.0
v22-0003-Get-rid-of-some-useless-code.patch (text/plain, 8.5K) download From febcd264d0ed39fb28c45ec9f2fdf0c10c9d65e5 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 8 Feb 2019 16:21:59 +0900 Subject: [PATCH v22 3/6] Get rid of some useless code * plancat.c no longer needs to load partition constraints, because even update/delete now use partprune.c's facilities * relation_excluded_by_constraints no longer needs to worry about fielding update/delete on partitioned tablesm, which in turn means InheritanceKind is no longer necessary --- src/backend/nodes/outfuncs.c | 1 - src/backend/optimizer/plan/createplan.c | 10 ----- src/backend/optimizer/plan/planner.c | 8 ---- src/backend/optimizer/prep/prepjointree.c | 1 - src/backend/optimizer/util/plancat.c | 73 ++++--------------------------- src/include/nodes/pathnodes.h | 15 ------- 6 files changed, 9 insertions(+), 99 deletions(-) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 10038a22cf..de84fb1576 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2208,7 +2208,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); WRITE_UINT_FIELD(qual_security_level); - WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind); WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasHavingQual); diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index c7645acad2..ab72c10d25 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -2054,12 +2054,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) /* * During setrefs.c, we'll need the grouping_map to fix up the cols lists * in GroupingFunc nodes. Save it for setrefs.c to use. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be grouping in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->grouping_map == NULL); root->grouping_map = grouping_map; @@ -2216,12 +2211,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) * with InitPlan output params. (We can't just do that locally in the * MinMaxAgg node, because path nodes above here may have Agg references * as well.) Save the mmaggregates list to tell setrefs.c to do that. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be aggregates in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->minmax_aggs == NIL); root->minmax_aggs = best_path->mmaggregates; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index fdf17d1b35..370391eefd 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -636,7 +636,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->grouping_map = NULL; root->minmax_aggs = NIL; root->qual_security_level = 0; - root->inhTargetKind = INHKIND_NONE; root->hasRecursion = hasRecursion; if (hasRecursion) root->wt_param_id = assign_special_exec_param(root); @@ -1309,13 +1308,6 @@ inheritance_planner(PlannerInfo *root) parent_rte->securityQuals = NIL; /* - * Mark whether we're planning a query to a partitioned table or an - * inheritance parent. - */ - subroot->inhTargetKind = - (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED; - - /* * Ignore a partitioned child. Instead, the paths of its children will * be added to subpaths. */ diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index aebe162713..8aef1f6535 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -894,7 +894,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->grouping_map = NULL; subroot->minmax_aggs = NIL; subroot->qual_security_level = 0; - subroot->inhTargetKind = INHKIND_NONE; subroot->hasRecursion = false; subroot->wt_param_id = -1; subroot->non_recursive_path = NULL; diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index d6dc83ca80..d8c1981b4d 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1266,36 +1266,6 @@ get_relation_constraints(PlannerInfo *root, } } - /* - * Append partition predicates, if any. - * - * For selects, partition pruning uses the parent table's partition bound - * descriptor, instead of constraint exclusion which is driven by the - * individual partition's partition constraint. - */ - if (enable_partition_pruning && root->parse->commandType != CMD_SELECT) - { - List *pcqual = RelationGetPartitionQual(relation); - - if (pcqual) - { - /* - * Run the partition quals through const-simplification similar to - * check constraints. We skip canonicalize_qual, though, because - * partition quals should be in canonical form already; also, - * since the qual is in implicit-AND format, we'd have to - * explicitly convert it to explicit-AND format and back again. - */ - pcqual = (List *) eval_const_expressions(root, (Node *) pcqual); - - /* Fix Vars to have the desired varno */ - if (varno != 1) - ChangeVarNodes((Node *) pcqual, 1, varno, 0); - - result = list_concat(result, pcqual); - } - } - table_close(relation, NoLock); return result; @@ -1419,40 +1389,15 @@ relation_excluded_by_constraints(PlannerInfo *root, /* * Skip further tests, depending on constraint_exclusion. */ - switch (constraint_exclusion) - { - case CONSTRAINT_EXCLUSION_OFF: - - /* - * Don't prune if feature turned off -- except if the relation is - * a partition. While partprune.c-style partition pruning is not - * yet in use for all cases (update/delete is not handled), it - * would be a UI horror to use different user-visible controls - * depending on such a volatile implementation detail. Therefore, - * for partitioned tables we use enable_partition_pruning to - * control this behavior. - */ - if (root->inhTargetKind == INHKIND_PARTITIONED) - break; - return false; - - case CONSTRAINT_EXCLUSION_PARTITION: - - /* - * When constraint_exclusion is set to 'partition' we only handle - * OTHER_MEMBER_RELs, or BASERELs in cases where the result target - * is an inheritance parent or a partitioned table. - */ - if ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) && - !(rel->reloptkind == RELOPT_BASEREL && - root->inhTargetKind != INHKIND_NONE && - rel->relid == root->parse->resultRelation)) - return false; - break; - - case CONSTRAINT_EXCLUSION_ON: - break; /* always try to exclude */ - } + if (constraint_exclusion == CONSTRAINT_EXCLUSION_OFF) + return false; + /* + * When constraint_exclusion is set to 'partition' we only handle + * OTHER_MEMBER_RELs. + */ + else if (constraint_exclusion == CONSTRAINT_EXCLUSION_PARTITION && + rel->reloptkind != RELOPT_OTHER_MEMBER_REL) + return false; /* * Check for self-contradictory restriction clauses. We dare not make diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index a9606fab15..5ca9082b14 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -82,18 +82,6 @@ typedef enum UpperRelationKind /* NB: UPPERREL_FINAL must be last enum entry; it's used to size arrays */ } UpperRelationKind; -/* - * This enum identifies which type of relation is being planned through the - * inheritance planner. INHKIND_NONE indicates the inheritance planner - * was not used. - */ -typedef enum InheritanceKind -{ - INHKIND_NONE, - INHKIND_INHERITED, - INHKIND_PARTITIONED -} InheritanceKind; - /*---------- * PlannerGlobal * Global information for planning/optimization @@ -325,9 +313,6 @@ struct PlannerInfo Index qual_security_level; /* minimum security_level for quals */ /* Note: qual_security_level is zero if there are no securityQuals */ - InheritanceKind inhTargetKind; /* indicates if the target relation is an - * inheritance child or partition or a - * partitioned table */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */ bool hasHavingQual; /* true if havingQual was non-null */ -- 2.11.0
v22-0004-Lazy-creation-of-RTEs-for-inheritance-children.patch (text/plain, 95.0K) download From 321981e88053463e39f221b8d9a5e2b982f35ff5 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 26 Oct 2018 16:45:59 +0900 Subject: [PATCH v22 4/6] Lazy creation of RTEs for inheritance children Currently, expand_inherited_tables called from subquery_planner opens all inheritance child tables and adds them to the query's range table and PlannerInfo in the form of AppendRelInfo's initially and later RelOptInfo's. For partitioning, that's pretty wasteful, because it's possible to determine only the partitions that will need to be scanned using partition pruning, which doesn't require opening the partitions themselves. However, the point at which expand_inherited_tables is called currently is too early to perform partition pruning. This commit rearranges things within the planner so that expand_inherited_tables can be called from query_planner after the latter has finished setting up the information needed to perform partition pruning. However that means the PlannerInfo arrays that store RangeTblEntry's, RelOptInfo's, and AppendRelInfo's need to be expanded if inheritance expansion leads to adding more relations to planning. There are also consequences around targetlist expansion. For example, preprocess_targetlist cannot add the junk columns entries needed for row mark handling until all of the inheritance children have been determined, because different children may need different types of junk columns to be added. So, the approach of late inheritance expansion required some interface tweaks for preprocess_targetlist(). For partitioning, although we don't create a RangeTblEntry and RelOptInfo for pruned partitions at make_one_rel time, partitionwise join code relies on the fact that even though partitions may have been pruned, they'd still own a RelOptInfo to handle the outer join case where the pruned partition appears on the nullable side of join. Partitionwise join code deals with that by allocating dummy RelOptInfos for pruned partitions that are based mostly on their parent's properties. There are some regression test diffs: 1. Caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child, as seen in the partition_aggregate.out test output. 2. Those in postgres_fdw.out are caused by the fact that junk columns required for row marking are added to reltarget->exprs later than user columns, because the row marking junk columns aren't added until the inheritance is expanded which as of this commit is later than it used to be as noted above. --- contrib/postgres_fdw/expected/postgres_fdw.out | 32 +- src/backend/nodes/outfuncs.c | 1 + src/backend/optimizer/path/allpaths.c | 239 +----- src/backend/optimizer/path/joinrels.c | 71 +- src/backend/optimizer/plan/initsplan.c | 49 -- src/backend/optimizer/plan/planmain.c | 10 + src/backend/optimizer/plan/planner.c | 64 +- src/backend/optimizer/plan/setrefs.c | 6 + src/backend/optimizer/prep/preptlist.c | 15 +- src/backend/optimizer/prep/prepunion.c | 3 + src/backend/optimizer/util/appendinfo.c | 45 +- src/backend/optimizer/util/inherit.c | 843 ++++++++++++++++------ src/backend/optimizer/util/plancat.c | 47 +- src/backend/optimizer/util/relnode.c | 107 +-- src/backend/partitioning/partprune.c | 44 +- src/include/nodes/pathnodes.h | 19 +- src/include/optimizer/appendinfo.h | 7 +- src/include/optimizer/pathnode.h | 1 + src/include/optimizer/plancat.h | 4 +- src/include/optimizer/prep.h | 3 +- src/test/regress/expected/partition_aggregate.out | 4 +- 21 files changed, 959 insertions(+), 655 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 029552b152..0c417cd360 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7128,15 +7128,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7166,15 +7166,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7203,15 +7203,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid @@ -7221,15 +7221,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (39 rows) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index de84fb1576..1c23b3d042 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2268,6 +2268,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_BOOL_FIELD(consider_partitionwise_join); WRITE_BITMAPSET_FIELD(top_parent_relids); WRITE_NODE_FIELD(partitioned_child_rels); + WRITE_UINT_FIELD(inh_root_parent); } static void diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 11fc64ff5d..abb52983d2 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -45,7 +45,6 @@ #include "optimizer/tlist.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -145,9 +144,6 @@ static void subquery_push_qual(Query *subquery, static void recurse_push_qual(Node *setOp, Query *topquery, RangeTblEntry *rte, Index rti, Node *qual); static void remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel); -static bool apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, - RelOptInfo *childrel, - RangeTblEntry *childRTE, AppendRelInfo *appinfo); /* @@ -388,6 +384,14 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, else if (rte->inh) { /* + * expand_inherited_tables may have proved that the relation is empty. + * For example, if it's a partitioned table with 0 partitions or all + * of its partitions are pruned. In that case nothing to do here. + */ + if (IS_DUMMY_REL(rel)) + return; + + /* * If it's a target relation, set the sizes of children instead. * Otherwise, we'll append the outputs of children, so process it as * an "append relation". @@ -980,29 +984,12 @@ set_inherited_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, int parentRTindex = rti; bool has_live_children = false; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); Assert(IS_SIMPLE_REL(rel)); - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - foreach(l, root->append_rel_list) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); @@ -1013,7 +1000,6 @@ set_inherited_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, ListCell *lc; List *translated_exprs, *child_target_exprs; - bool childpruned; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1021,32 +1007,23 @@ set_inherited_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); + Assert(childrel != NULL); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - childpruned = (did_pruning && - !bms_is_member(appinfo->child_relid, live_children)); - /* - * Unless the child is pruned, 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 baserestrictinfo quals. + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals or quals that contradict its + * constraints. */ - if (childpruned || - !apply_child_basequals(root, rel, childrel, childRTE, appinfo) || - relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* This child need not be scanned; skip it. */ - set_dummy_rel_pathlist(childrel); + if (IS_DUMMY_REL(childrel)) continue; - } /* * Add missing Vars to child's reltarget. @@ -1185,8 +1162,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -1194,32 +1169,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Assert(IS_SIMPLE_REL(rel)); /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, we will bubble up - * the indexes of partitioned relations that appear down in the tree, so - * that when we've created Paths for all the children, the root - * partitioned table's list will contain all such indexes. - */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - rel->partitioned_child_rels = list_make1_int(rti); - - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - - /* * If this is a partitioned baserel, set the consider_partitionwise_join * flag; currently, we only consider partitionwise joins with the baserel * if its targetlist doesn't contain a whole-row Var. @@ -1258,7 +1207,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, RelOptInfo *childrel; ListCell *parentvars; ListCell *childvars; - bool childpruned; /* append_rel_list contains all append rels; ignore others */ if (appinfo->parent_relid != parentRTindex) @@ -1266,34 +1214,26 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); + Assert(childrel != NULL); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - childpruned = (did_pruning && - !bms_is_member(appinfo->child_relid, live_children)); /* - * Unless the child is pruned, 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 baserestrictinfo quals. + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals or quals that contradict its + * constraints. */ - if (childpruned || - !apply_child_basequals(root, rel, childrel, childRTE, appinfo) || - relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* This child need not be scanned; skip it. */ - set_dummy_rel_pathlist(childrel); + if (IS_DUMMY_REL(childrel)) continue; - } /* - * CE failed, so finish copying/modifying targetlist and join quals. + * Copy/Modify targetlist. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -2830,6 +2770,7 @@ inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) continue; childrel = find_base_rel(root, appinfo->child_relid); + Assert(childrel != NULL); /* Ignore excluded/pruned children. */ if (IS_DUMMY_REL(childrel)) @@ -3934,134 +3875,6 @@ generate_partitionwise_join_paths(PlannerInfo *root, RelOptInfo *rel) list_free(live_children); } -/* - * apply_child_basequals - * Populate childrel's quals based on rel's quals, translating them using - * appinfo. - * - * If any of the resulting clauses evaluate to false or NULL, we return false - * and don't apply any quals. Caller can mark the relation as a dummy rel in - * this case, since it needn't be scanned. - * - * If any resulting clauses evaluate to true, they're unnecessary and we don't - * apply then. - */ -static bool -apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, - RelOptInfo *childrel, RangeTblEntry *childRTE, - AppendRelInfo *appinfo) -{ - List *childquals; - Index cq_min_security; - ListCell *lc; - - /* - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities for - * const-simplification, and perhaps even pseudoconstant quals. Therefore, - * transform each RestrictInfo separately to see if it reduces to a - * constant or pseudoconstant. (We must process them separately to keep - * track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - return false; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. (Currently - * this can only happen in appendrels originating from UNION ALL; - * inheritance child tables don't have their own securityQuals, see - * expand_inherited_rtentry().) Pull any such securityQuals up into the - * baserestrictinfo for the child. This is similar to - * process_security_barrier_quals() for the parent rel, except that we - * can't make any general deductions from such quals, since they don't - * hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - return true; -} - /***************************************************************************** * DEBUG SUPPORT *****************************************************************************/ diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index dfbbfdac6d..6f77d2a0f4 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -21,6 +21,7 @@ #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" +#include "optimizer/tlist.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -52,6 +53,9 @@ static SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, Relids left_relids, Relids right_relids); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + int partidx); /* @@ -1384,6 +1388,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (IS_SIMPLE_REL(rel1) && child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts); + if (IS_SIMPLE_REL(rel1) && child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts); + /* * If a child table has consider_partitionwise_join=false, it means * that it's a dummy relation for which we skipped setting up tlist @@ -1462,8 +1471,14 @@ update_child_rel_info(PlannerInfo *root, (Node *) rel->reltarget->exprs, 1, &appinfo); - /* Make child entries in the EquivalenceClass as well */ - if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) + /* + * Make child entries in the EquivalenceClass as well. If the childrel + * appears to be a dummy one (one built by build_dummy_partition_rel()), + * no need to make any new entries, because anything that'd need those + * can instead use the parent's (rel). + */ + if (childrel->relid != rel->relid && + (rel->has_eclass_joins || has_useful_pathkeys(root, rel))) add_child_rel_equivalences(root, appinfo, rel, childrel); childrel->has_eclass_joins = rel->has_eclass_joins; } @@ -1674,3 +1689,55 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +static RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx) +{ + RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + AppendRelInfo *appinfo = make_append_rel_info(parent, parentrte, + parent->tupdesc, + parentrte->relid, + parent->reltype, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 2afc3f1dfe..5e8a8b1eee 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -419,7 +419,6 @@ void create_lateral_join_info(PlannerInfo *root) { bool found_laterals = false; - Relids prev_parents PG_USED_FOR_ASSERTS_ONLY = NULL; Index rti; ListCell *lc; @@ -618,54 +617,6 @@ create_lateral_join_info(PlannerInfo *root) bms_add_member(brel2->lateral_referencers, rti); } } - - /* - * Lastly, propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - * - * It's possible for child rels to have their own children, in which case - * the topmost parent's lateral info must be propagated all the way down. - * This code handles that case correctly so long as append_rel_list has - * entries for child relationships before grandchild relationships, which - * is an okay assumption right now, but we'll need to be careful to - * preserve it. The assertions below check for incorrect ordering. - */ - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); - RelOptInfo *parentrel = root->simple_rel_array[appinfo->parent_relid]; - RelOptInfo *childrel = root->simple_rel_array[appinfo->child_relid]; - - /* - * If we're processing a subquery of a query with inherited target rel - * (cf. inheritance_planner), append_rel_list may contain entries for - * tables that are not part of the current subquery and hence have no - * RelOptInfo. Ignore them. We can ignore dead rels, too. - */ - if (parentrel == NULL || !IS_SIMPLE_REL(parentrel)) - continue; - - /* Verify that children are processed before grandchildren */ -#ifdef USE_ASSERT_CHECKING - prev_parents = bms_add_member(prev_parents, appinfo->parent_relid); - Assert(!bms_is_member(appinfo->child_relid, prev_parents)); -#endif - - /* OK, propagate info down */ - Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - Assert(childrel->direct_lateral_relids == NULL); - childrel->direct_lateral_relids = parentrel->direct_lateral_relids; - Assert(childrel->lateral_relids == NULL); - childrel->lateral_relids = parentrel->lateral_relids; - Assert(childrel->lateral_referencers == NULL); - childrel->lateral_referencers = parentrel->lateral_referencers; - } } diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index c7f2323852..ae570e6abe 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -284,6 +284,16 @@ query_planner(PlannerInfo *root, List *tlist, } /* + * Expand RT entries that represent inherited or partitioned tables. + * This will perform partition pruning on partitioned tables in the + * original range table and also recursively on any child partitioned + * tables that were added by the expansion of the original parent(s). + * As new entries are added to the range table, various arrays in the + * PlannerInfo will be expanded accordingly. + */ + expand_inherited_tables(root); + + /* * Add child subroots needed during planning for individual child targets. */ if (parse->resultRelation && diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 370391eefd..ae612350ef 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -25,6 +25,7 @@ #include "access/table.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -716,27 +717,24 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } /* * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). + * pullup (so that all non-inherited RTEs are present). */ preprocess_rowmarks(root); /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain RTE_RELATION entries, so it's OK (and marginally more efficient) - * to do it after checking for joins and other special RTEs. We must do - * this after pulling up subqueries, else we'd fail to handle inherited - * tables in subqueries. - */ - expand_inherited_tables(root); - - /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -1245,7 +1243,9 @@ inheritance_planner(PlannerInfo *root) * of the parent. */ root->unexpanded_tlist = list_copy(root->parse->targetList); - tlist = preprocess_targetlist(root); + + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root, false); root->processed_tlist = tlist; qp_extra.tlist = tlist; qp_extra.activeWindows = NIL; @@ -1255,9 +1255,11 @@ inheritance_planner(PlannerInfo *root) /* * If it turned out during query planning that all the children are dummy * (pruned or excluded by constraints), no need to do the steps below. - * Let grouping_planner finish up the final path. + * For regular inheritance case, it's possible that we only need to modify + * the parent table. Let grouping_planner finish up the final path. */ - if (IS_DUMMY_REL(planned_rel)) + parent_rte = planner_rt_fetch(top_parentRTindex, root); + if (!parent_rte->inh || IS_DUMMY_REL(planned_rel)) { grouping_planner(root, false, planned_rel, 0.0); return; @@ -1273,7 +1275,6 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = planner_rt_fetch(top_parentRTindex, root); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1602,14 +1603,19 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, */ if (planned_rel == NULL) { - tlist = preprocess_targetlist(root); + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root, false); /* - * We are now done hacking up the query's targetlist. Most of the - * remaining planning work will be done with the PathTarget - * representation of tlists, but save aside the full representation - * so that we can transfer its decoration (resnames etc) to the - * topmost tlist of the finished Plan. + * We are now mostly done hacking up the query's targetlist. More + * columns might be added during query_planner after inheritance + * expansion in make_one_rel, because some children may require + * different column than the parent for row mark handling; see + * add_rowmark_junk_columns(). Most of the remaining planning + * work will be done with the PathTarget representation of tlists, + * but save aside the full representation so that we can transfer + * its decoration (resnames etc) to the topmost tlist of the + * finished Plan. */ root->processed_tlist = tlist; } @@ -2385,7 +2391,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2410,7 +2416,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } @@ -6847,6 +6853,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -6948,6 +6958,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 0213a37670..154ccda432 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -326,6 +326,12 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) UPPERREL_FINAL, NULL))) add_rtes_to_flat_rtable(rel->subroot, true); } + /* + * A NULL rel also means an unplanned subquery rte, so apply + * flatten_unplanned_rtes. + */ + else + flatten_unplanned_rtes(glob, rte); } rti++; } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 5392d1a561..41a57d16b2 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -65,9 +65,13 @@ static List *expand_targetlist(List *tlist, int command_type, * * As a side effect, if there's an ON CONFLICT UPDATE clause, its targetlist * is also preprocessed (and updated in-place). + * + * inheritance_expanded specifies whether inheritance tables are expanded + * as of calling this function. Caller must specify it because that affects + * which row marking related junk columns get added to the targetlist. */ List * -preprocess_targetlist(PlannerInfo *root) +preprocess_targetlist(PlannerInfo *root, bool inheritance_expanded) { Query *parse = root->parse; int result_relation = parse->resultRelation; @@ -134,6 +138,15 @@ preprocess_targetlist(PlannerInfo *root) if (rc->rti != rc->prti) continue; + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which will change the parent row + * mark's allMarkTypes fields. + */ + if (rc->isParent && !inheritance_expanded) + continue; + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) { /* Need to fetch TID */ diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 55eeb5127c..7bed914f35 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -31,12 +31,15 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" #include "optimizer/cost.h" +#include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index 12727ce97f..7d4a6aadbf 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -33,10 +33,10 @@ typedef struct AppendRelInfo **appinfos; } adjust_appendrel_attrs_context; -static void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, adjust_appendrel_attrs_context *context); static List *adjust_inherited_tlist(List *tlist, @@ -50,18 +50,20 @@ static EquivalenceClass * adjust_inherited_eq_class(PlannerInfo *root, * Build an AppendRelInfo for the parent-child pair */ AppendRelInfo * -make_append_rel_info(Relation parentrel, Relation childrel, - Index parentRTindex, Index childRTindex) +make_append_rel_info(RelOptInfo *parent, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childtype, + Index childRTindex) { AppendRelInfo *appinfo = makeNode(AppendRelInfo); - appinfo->parent_relid = parentRTindex; + appinfo->parent_relid = parent->relid; appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->rd_rel->reltype; - appinfo->child_reltype = childrel->rd_rel->reltype; - make_inh_translation_list(parentrel, childrel, childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = RelationGetRelid(parentrel); + appinfo->parent_reltype = parent->reltype; + appinfo->child_reltype = childtype; + make_inh_translation_list(parent->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; return appinfo; } @@ -74,14 +76,11 @@ make_append_rel_info(Relation parentrel, Relation childrel, * For paranoia's sake, we match type/collation as well as attribute name. */ static void -make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, - List **translated_vars) +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) { List *vars = NIL; - TupleDesc old_tupdesc = RelationGetDescr(oldrelation); - TupleDesc new_tupdesc = RelationGetDescr(newrelation); - Oid new_relid = RelationGetRelid(newrelation); int oldnatts = old_tupdesc->natts; int newnatts = new_tupdesc->natts; int old_attno; @@ -111,7 +110,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, * When we are generating the "translation list" for the parent table * of an inheritance set, no need to search for matches. */ - if (oldrelation == newrelation) + if (from_rel == to_rel) { vars = lappend(vars, makeVar(newvarno, (AttrNumber) (old_attno + 1), @@ -137,10 +136,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, { HeapTuple newtup; - newtup = SearchSysCacheAttName(new_relid, attname); + newtup = SearchSysCacheAttName(to_rel, attname); if (!newtup) elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; ReleaseSysCache(newtup); @@ -150,10 +149,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, /* Found it, check type and collation match */ if (atttypid != att->atttypid || atttypmod != att->atttypmod) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); if (attcollation != att->attcollation) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); vars = lappend(vars, makeVar(newvarno, (AttrNumber) (new_attno + 1), diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 0ca320abcc..a4664c38a8 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -18,33 +18,47 @@ #include "access/table.h" #include "catalog/partition.h" #include "catalog/pg_inherits.h" +#include "catalog/pg_type.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" #include "optimizer/inherit.h" +#include "optimizer/optimizer.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" +#include "optimizer/plancat.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" +#include "partitioning/partprune.h" #include "utils/rel.h" static PlannerInfo *create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo); -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); +static void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti); +static void expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, + RangeTblEntry *rte, Index rti, RelOptInfo *rel); static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); - +static RelOptInfo *build_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); +static bool apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *childrel, + RangeTblEntry *childRTE, AppendRelInfo *appinfo); /* * expand_inherited_tables @@ -52,37 +66,145 @@ static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, * into an "append relation". At the conclusion of this process, * the "inh" flag is set in all and only those RTEs that are append * relation parents. + * + * Note that although we're calling the combined output of inheritance set + * relations an "append relation" here, the caller may not always want to + * combine the relations. For example, if the parent of the inheritance + * set is the query's target relation, each child relation is processed + * on its own as the query's target relation. */ void expand_inherited_tables(PlannerInfo *root) { - Index nrtes; + int orig_rtable_size; Index rti; - ListCell *rl; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; /* * expand_inherited_rtentry may add RTEs to parse->rtable. The function is * expected to recursively handle any RTEs that it creates with inh=true. * So just scan as far as the original end of the rtable list. */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) + for (rti = 1; rti < orig_rtable_size; rti++) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + if (rte->inh) + expand_inherited_rtentry(root, brel, rte, rti); } } /* * expand_inherited_rtentry - * Check whether a rangetable entry represents an inheritance set. - * If so, add entries for all the child tables to the query's - * rangetable, and build AppendRelInfo nodes for all the child tables - * and add them to root->append_rel_list. If not, clear the entry's - * "inh" flag to prevent later code from looking for AppendRelInfos. + * This initializes RelOptInfos for inheritance child relations if the + * passed-in relation has any + * + * 'rel' is the parent relation, whose range table entry ('rte') has been + * marked to require adding children. Parent could either be a subquery (if + * we flattened UNION ALL query) or a table that's known to have (or once had) + * inheritance children. The latter consists of both regular inheritance + * parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as a child to be scanned as part of the + * inheritance set. The original RTE is considered to represent the whole + * inheritance set. + */ +static void +expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_inheritance_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_nonpartitioned_inherited_rtentry(root, rte, rti, rel); + } +} + +/* + * expand_nonpartitioned_inherited_rtentry + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. * * Note that the original RTE is considered to represent the whole * inheritance set. The first of the generated RTEs is an RTE for the same @@ -93,55 +215,35 @@ expand_inherited_tables(PlannerInfo *root) * regular inheritance, a parent RTE must always have at least two associated * AppendRelInfos: one corresponding to the parent table as a simple member of * inheritance set and one or more corresponding to the actual children. - * Since a partitioned table is not scanned, it might have only one associated - * AppendRelInfo. */ static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, + RangeTblEntry *rte, + Index rti, + RelOptInfo *rel) { Oid parentOID; PlanRowMark *oldrc; - Relation oldrelation; - LOCKMODE lockmode; + LOCKMODE lockmode = rte->rellockmode; List *inhOIDs; ListCell *l; + int num_children; + int num_children_added = 0; - /* Does RT entry allow inheritance? */ - if (!rte->inh) - return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } - /* Fast path for common case of childless table */ + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); parentOID = rte->relid; - if (!has_subclass(parentOID)) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * The rewriter should already have obtained an appropriate lock on each - * relation named in the query. However, for each child relation we add - * to the query, we must obtain an appropriate lock, because this will be - * the first use of those relations in the parse/rewrite/plan pipeline. - * Child rels should use the same lockmode as their parent. - */ - lockmode = rte->rellockmode; /* Scan for all members of inheritance set, acquire needed locks */ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); /* * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite above has_subclass() check, if table - * once had a child but no longer does. + * case. This could happen despite has_subclass() check performed by + * subquery_planner, if table once had a child but no longer does. */ - if (list_length(inhOIDs) < 2) + num_children = list_length(inhOIDs); + if (num_children < 2) { /* Clear flag before returning */ rte->inh = false; @@ -149,216 +251,221 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } /* - * If parent relation is selected FOR UPDATE/SHARE, we need to mark its - * PlanRowMark as isParent = true, and generate a new PlanRowMark for each - * child. + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. */ oldrc = get_plan_rowmark(root->rowMarks, rti); - if (oldrc) - oldrc->isParent = true; + Assert(oldrc == NULL || oldrc->isParent); /* - * Must open the parent relation to examine its tupdesc. We need not lock - * it; we assume the rewriter already did. + * Must expand PlannerInfo arrays by num_children before we can add + * children. */ - oldrelation = table_open(parentOID, NoLock); + Assert(num_children > 0); + expand_planner_arrays(root, num_children); - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) + foreach(l, inhOIDs) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); - - /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. - */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); - } - else - { - List *appinfos = NIL; + Oid childOID = lfirst_oid(l); + Relation newrelation; RangeTblEntry *childrte; Index childRTindex; + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. */ - foreach(l, inhOIDs) + if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = table_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - table_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - table_close(newrelation, NoLock); + table_close(newrelation, lockmode); + continue; } - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); + (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation, &childrte, + &childRTindex); + Assert(childrte != NULL); + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + Assert(childRTindex > 0); + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; } - table_close(oldrelation, NoLock); + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } } /* * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) + Index parentRTindex, RelOptInfo *parentrel) { + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; - check_stack_depth(); - - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + if (bms_num_members(partindexes) > 0) + expand_planner_arrays(root, bms_num_members(partindexes)); /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. */ - if (partdesc->nparts == 0) - { - parentrte->inh = false; - return; - } + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); - for (i = 0; i < partdesc->nparts; i++) + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) { - Oid childOID = partdesc->oids[i]; - Relation childrel; + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + RangeTblEntry *childrte; + Index childRTindex; /* Open rel; we already have required locks */ - childrel = table_open(childOID, NoLock); + newrelation = table_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation, + &childrte, &childRTindex); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + /* Close child relations, but keep locks */ + table_close(newrelation, NoLock); + + /* If the child is partitioned itself, expand it too. */ + if (childrel->part_scheme) + { + Assert(childrte->inh); expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); + childrel); + } + } - /* Close child relation, but keep locks */ - table_close(childrel, NoLock); + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); } } /* - * expand_single_inheritance_child - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) + * The return value is the RelOptInfo that's added. * * PlanRowMarks still carry the top-parent's RTI, and the top-parent's * allMarkTypes field still accumulates values from all descendents. * * "parentrte" and "parentRTindex" are immediate parent's RTE and * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. */ -static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); Oid childOID = RelationGetRelid(childrel); RangeTblEntry *childrte; Index childRTindex; AppendRelInfo *appinfo; + RelOptInfo *childrelopt; /* * Build an RTE for the child, and attach to query's rangetable list. We * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh = false. Also, set requiredPerms to zero since - * all required permissions checks are done on the original RTE. Likewise, - * set the child's securityQuals to empty, because we only want to apply - * the parent's RLS conditions regardless of what RLS properties + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties * individual children may have. (This is an intentional choice to make * inherited RLS work like regular permissions checks.) The parent * securityQuals will be propagated to children along with other base @@ -368,49 +475,42 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childrte_p = childrte; childrte->relid = childOID; childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) - childrte->inh = true; - else - childrte->inh = false; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); childrte->requiredPerms = 0; childrte->securityQuals = NIL; parse->rtable = lappend(parse->rtable, childrte); childRTindex = list_length(parse->rtable); *childRTindex_p = childRTindex; - /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. - */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) - { - appinfo = make_append_rel_info(parentrel, childrel, - parentRTindex, childRTindex); - *appinfos = lappend(*appinfos, appinfo); + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + RelationGetDescr(childrel), + RelationGetRelid(childrel), + RelationGetForm(childrel)->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); } /* @@ -442,6 +542,147 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need to create one, + * because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_inheritance_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; +} + +/* + * build_inheritance_child_rel + * Build a RelOptInfo for child relation of an inheritance set + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields based on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turn out to be self-contradictory, + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + * + * It's possible for child rels to have their own children, in which case + * the topmost parent's lateral info must be propagated all the way down. + * That's ensured by having childrel be expanded via this same path. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + */ + if (!apply_child_basequals(root, parent, childrel, childRTE, appinfo) || + relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; } /* @@ -594,7 +835,7 @@ create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) * column, filling placeholder entries for dropped columns, etc., all of * which occurs with the child's TupleDesc. */ - tlist = preprocess_targetlist(subroot); + tlist = preprocess_targetlist(subroot, true); subroot->processed_tlist = tlist; /* Add any newly added Vars to the child RelOptInfo. */ @@ -609,3 +850,131 @@ create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) return subroot; } + +/* + * apply_child_basequals + * Populate childrel's quals based on rel's quals, translating them using + * appinfo. + * + * If any of the resulting clauses evaluate to false or NULL, we return false + * and don't apply any quals. Caller can mark the relation as a dummy rel in + * this case, since it needn't be scanned. + * + * If any resulting clauses evaluate to true, they're unnecessary and we don't + * apply then. + */ +static bool +apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *childrel, RangeTblEntry *childRTE, + AppendRelInfo *appinfo) +{ + List *childquals; + Index cq_min_security; + ListCell *lc; + + /* + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities for + * const-simplification, and perhaps even pseudoconstant quals. Therefore, + * transform each RestrictInfo separately to see if it reduces to a + * constant or pseudoconstant. (We must process them separately to keep + * track of the security level of each qual.) + */ + childquals = NIL; + cq_min_security = UINT_MAX; + foreach(lc, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, + (Node *) rinfo->clause, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + return false; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + /* track minimum security level among child quals */ + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might have + * securityQuals associated with this particular child node. (Currently + * this can only happen in appendrels originating from UNION ALL; + * inheritance child tables don't have their own securityQuals, see + * expand_inherited_rtentry().) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except that we + * can't make any general deductions from such quals, since they don't + * hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* not likely that we'd see constants here, so no check */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, false, false, + security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* + * OK, we've got all the baserestrictinfo quals for this child. + */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + return true; +} diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index d8c1981b4d..83c6a956b7 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -107,20 +107,20 @@ static void set_baserel_partition_key_exprs(Relation relation, * important for it. */ void -get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) +get_relation_info(PlannerInfo *root, RangeTblEntry *rte, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; bool hasindex; List *indexinfos = NIL; + bool inhparent = rte->inh; /* * We need not lock the relation since it was already locked, either by * the rewriter or when expand_inherited_rtentry() added it to the query's * rangetable. */ - relation = table_open(relationObjectId, NoLock); + relation = heap_open(rte->relid, NoLock); /* Temporary and unlogged relations are inaccessible during recovery. */ if (!RelationNeedsWAL(relation) && RecoveryInProgress()) @@ -446,11 +446,32 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, get_relation_foreign_keys(root, rel, relation, inhparent); /* - * Collect info about relation's partitioning scheme, if any. Only - * inheritance parents may be partitioned. + * Collect some additional information for inheritance parents. */ - if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - set_relation_partition_info(root, rel, relation); + if (inhparent) + { + /* + * We'll need the TupleDesc when initializing the child relation. + * A copy is being made because concurrent changes might drop + * the relcache entry. That's possible because ALTER TABLE + * child_table NO INHERIT parent_table only requires an + * AccessShareLock on parent_table. + */ + rel->tupdesc = CreateTupleDescCopy(RelationGetDescr(relation)); + rel->reltype = RelationGetForm(relation)->reltype; + + /* + * If partitioned, also save the information of partitioning scheme, + * and whether the query updates any of the partition key columns. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + set_relation_partition_info(root, rel, relation); + root->partColsUpdated |= has_partition_attrs(relation, + rte->updatedCols, + NULL); + } + } table_close(relation, NoLock); @@ -460,7 +481,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * removing an index, or adding a hypothetical index to the indexlist. */ if (get_relation_info_hook) - (*get_relation_info_hook) (root, relationObjectId, inhparent, rel); + (*get_relation_info_hook) (root, rte->relid, rte->inh, rel); } /* @@ -2027,16 +2048,20 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; + + /* + * Since we must've taken a lock on the table, it's okay to simply copy + * the pointers to relcache data here. + */ + rel->part_oids = partdesc->oids; + rel->boundinfo = partdesc->boundinfo; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 4130514952..f6fca75677 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -132,6 +132,50 @@ setup_append_rel_array(PlannerInfo *root) } /* + * expand_planner_arrays + * Expand the PlannerInfo arrays by add_size members and initialize the + * the newly added bytes with zero + */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size; + + Assert(add_size > 0); + + new_size = root->simple_rel_array_size + add_size; + + /* Expand various arrays and 0-initialize added bytes. */ + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + + if (root->append_rel_array) + { + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + } + else + { + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + } + + root->simple_rel_array_size = new_size; +} + +/* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. */ @@ -195,6 +239,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->joininfo = NIL; rel->has_eclass_joins = false; rel->consider_partitionwise_join = false; /* might get changed later */ + rel->top_parent_relids = NULL; /* might be changed later */ rel->part_scheme = NULL; rel->nparts = 0; rel->boundinfo = NULL; @@ -203,6 +248,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->partexprs = NULL; rel->nullable_partexprs = NULL; rel->partitioned_child_rels = NIL; + rel->inh_root_parent = 0; /* might be changed later */ /* * Pass top parent's relids down the inheritance hierarchy. If the parent @@ -216,16 +262,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->top_parent_relids = parent->top_parent_relids; else rel->top_parent_relids = bms_copy(parent->relids); + + /* + * For inheritance child relations, we also set inh_root_parent. + * Note that 'parent' might itself be a child (a sub-partitioned + * partition), in which case we simply use its value of + * inh_root_parent. + */ + if (parent->rtekind == RTE_RELATION) + rel->inh_root_parent = parent->inh_root_parent > 0 ? + parent->inh_root_parent : + parent->relid; } - else - rel->top_parent_relids = NULL; /* Check type of rtable entry */ switch (rte->rtekind) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte, rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -273,52 +328,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); - /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. - */ - if (rte->inh) - { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); - - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; - } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); - } - return rel; } diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 8c9721935d..472a6cd331 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -45,6 +45,8 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "partitioning/partprune.h" @@ -433,17 +435,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -537,23 +545,20 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ @@ -561,6 +566,13 @@ prune_append_rel_partitions(RelOptInfo *rel) return NULL; /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. */ @@ -587,15 +599,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 5ca9082b14..0730284607 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -15,6 +15,7 @@ #define PATHNODES_H #include "access/sdir.h" +#include "access/tupdesc.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/params.h" @@ -370,7 +371,6 @@ struct PlannerInfo }; - /* * In places where it's known that simple_rte_array[] must have been prepared * already, we just index into it to fetch RTEs. In code that might be @@ -723,11 +723,28 @@ typedef struct RelOptInfo int nparts; /* number of partitions */ struct PartitionBoundInfoData *boundinfo; /* Partition bounds */ List *partition_qual; /* partition constraint */ + Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ + + /* + * For inheritance children, this is the RT index of inheritance table + * mentioned in the query from which this relation originated. + * top_parent_relids cannot be used for this, because if the inheritance + * root table is itself under UNION ALL, top_parent_relids contains the + * RT index of UNION ALL parent subquery. + */ + Index inh_root_parent; + + /* + * Set only if this is an inheritance parent relation. This information + * is needed when initializing the planning info for children. + */ + TupleDesc tupdesc; /* A "copy" of the table's tuple desriptor */ + Oid reltype; /* Table's reltype */ } RelOptInfo; /* diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h index 790914c1b0..c0b8db9a62 100644 --- a/src/include/optimizer/appendinfo.h +++ b/src/include/optimizer/appendinfo.h @@ -17,9 +17,10 @@ #include "nodes/pathnodes.h" #include "utils/relcache.h" -extern AppendRelInfo *make_append_rel_info(Relation parentrel, - Relation childrel, - Index parentRTindex, Index childRTindex); +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parent, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childtype, + Index childRTindex); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 574bb85b50..3a803b3fd0 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -277,6 +277,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index c337f047cb..04731f532f 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -25,8 +25,8 @@ typedef void (*get_relation_info_hook_type) (PlannerInfo *root, extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; -extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); +extern void get_relation_info(PlannerInfo *root, RangeTblEntry *rte, + RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index a9b2c9026c..27de05ba3e 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -34,7 +34,8 @@ extern Relids get_relids_for_join(Query *query, int joinrelid); /* * prototypes for preptlist.c */ -extern List *preprocess_targetlist(PlannerInfo *root); +extern List *preprocess_targetlist(PlannerInfo *root, + bool inheritance_expanded); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 6bc106831e..1450cef057 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) -- 2.11.0
v22-0005-Teach-planner-to-only-process-unpruned-partition.patch (text/plain, 6.9K) download From caafb5898aa9dcb5ec2c48bb1b5d0682954f7b58 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 15 Oct 2018 10:59:24 +0900 Subject: [PATCH v22 5/6] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/inherit.c | 1 + src/backend/optimizer/util/relnode.c | 3 +++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/pathnodes.h | 4 ++++ 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 6f77d2a0f4..fdbf7689ed 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1452,6 +1452,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index ae612350ef..edc6e06d7f 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6845,7 +6845,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -6853,9 +6855,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -6936,7 +6936,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -6948,7 +6947,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -6958,9 +6959,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index a4664c38a8..d9b8f58198 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -365,6 +365,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, /* Perform pruning. */ partindexes = prune_append_rel_partitions(parentrel); + parentrel->live_parts = partindexes; /* Must expand PlannerInfo arrays before we can add children. */ if (bms_num_members(partindexes) > 0) diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index f6fca75677..03c9ded294 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1743,6 +1743,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 472a6cd331..babb7d3406 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -438,29 +438,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 0730284607..8975c653fc 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -726,6 +726,10 @@ typedef struct RelOptInfo Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Indexes into part_rels of the non-NULL + * RelOptInfos of unpruned partitions; exists + * to avoid having to iterate over the entire + * part_rels array to filter NULL entries. */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.11.0
v22-0006-Do-not-lock-all-partitions-at-the-beginning.patch (text/plain, 1.9K) download From cb7e34ab21a0cf009788d4c9c72bed2e7b70ee6d Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 12 Oct 2018 19:12:53 +0900 Subject: [PATCH v22 6/6] Do not lock all partitions at the beginning --- src/backend/optimizer/util/inherit.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index d9b8f58198..901dcee921 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -332,10 +332,6 @@ expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, * * Partitions are added to the query in order in which they are found in * the parent's PartitionDesc. - * - * Note: even though only the unpruned partitions will be added to the - * resulting plan, this still locks *all* partitions via find_all_inheritors - * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, @@ -349,10 +345,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, parentrel->inh_root_parent : parentRTindex; - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - /* * Initialize partitioned_child_rels to contain this RT index. * @@ -389,8 +381,11 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, RangeTblEntry *childrte; Index childRTindex; - /* Open rel; we already have required locks */ - newrelation = table_open(childOID, NoLock); + /* + * Open rel; this's the first time of opening partitions for this + * query, so take the appropriate locks. + */ + newrelation = table_open(childOID, lockmode); Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-02-18 00:57  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 0 replies; 265+ messages in thread

From: Imai, Yoshikazu @ 2019-02-18 00:57 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Amit Langote <amitlangote09@gmail.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; David Rowley <david.rowley@2ndquadrant.com>; pgsql-hackers

Amit-san

Sorry for my late reply. I had another work to do.

On Fri, Feb 8, 2019 at 9:13 AM, Amit Langote wrote:
> On 2019/02/08 13:44, Imai, Yoshikazu wrote:
> > 3.
> > 0001: line 1919-1920
> >
> > -		case CONSTRAINT_EXCLUSION_ON:
> > -			break;				/* always try
> to exclude */
> >
> > CONSTRAINT_EXCLUSION_ON is no longer used, so should we remove it also
> from guc parameters?
> 
> Well, we haven't removed the "on" setting itself.

Ah, I understand.

> Okay, I've broken down those changes into separate patches, so that
> cleanup hunks are not fixed with other complex changes.
> 
> 0001 is now a patch to remove duplicate code from set_append_rel_size.
> It combines multiple blocks that have the same body doing
> set_dummy_rel_pathlist().
> 
> 0002 is the "overhaul inherited update/delete planning"
> 
> 0003 is a cleanup patch that gets rid of some code that is rendered useless
> due to 0002 (partitioned tables no longer use constraint exclusion)

Thanks for doing these.

> I think 0001 can be committed on its own.

+1.

In commit message:

s/contradictory quals found/contradictory quals are found/
s/child excluded/child is excluded/

I think others in 0001 are ok.

> 0002+0003 can be committed
> together.
>
> 0004-0006 are the patches that were previously 0002-0004.

I will do code review of v22 patches again and send notes as soon as possible.

Yoshikazu Imai



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-18 17:27  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 2 replies; 265+ messages in thread

From: Tom Lane @ 2019-02-18 17:27 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Amit Langote <amitlangote09@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> writes:
> [ v22 patch set ]

I started to look at this, and immediately choked on the 0001 patch:

        if (childpruned ||
            !apply_child_basequals(root, rel, childrel, childRTE, appinfo) ||
            relation_excluded_by_constraints(root, childrel, childRTE))
        {

Frankly, that code is just horrid.  Having a function with side effects
in an if-test is questionable at the best of times, and having it be
the second of three conditions (which the third condition silently depends
on) is unreadable and unmaintainable.

I think the existing code here is considerably cleaner than what this
patch proposes.

I suppose you are doing this because you intend to jam some additional
cleanup code into the successfully-pruned-it code path, but if said
code is really too bulky to have multiple copies of, couldn't you
put it into a subroutine?  You're not going to be able to get to only
one copy of such cleanup anyhow, because there is another early-exit
further down, for the case where set_rel_size detects dummy-ness.

			regards, tom lane




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-18 19:42  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 1 reply; 265+ messages in thread

From: Tom Lane @ 2019-02-18 19:42 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Amit Langote <amitlangote09@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> writes:
> [ v22 patch set ]

I did some review of the 0002 patch.  I like the general idea,
but there are a lot of details I don't much like.

Probably the biggest complaint is that I don't like what you did to the
API of grouping_planner(): it's ugly and unprincipled.  Considering that
only a tiny fraction of what grouping_planner does is actually relevant to
inherited UPDATES/DELETEs, maybe we should factor that part out --- or,
heavens, just duplicate some code --- so that inheritance_planner doesn't
need to call grouping_planner at all.  (I see that you have another patch
in the queue that proposes to fix this through a rather massive
refactoring of grouping_planner, but I do not think I like that approach.
grouping_planner does perhaps need refactoring, but I don't want to drive
such work off an arbitrary-and-dubious requirement that it shouldn't call
query_planner.)

Another point is that I don't like this division of labor between
equivclass.c and appendinfo.c.  I don't like exposing add_eq_member
globally --- that's just an invitation for code outside equivclass.c to
break the fundamental invariants of ECs --- and I also think you've taught
appendinfo.c far more than it ought to know about the innards of ECs.
I'd suggest putting all of that logic into equivclass.c, with a name along
the lines of translate_equivalence_class_to_child.  That would reverse the
dependency, in that equivclass.c would now need to call
adjust_appendrel_attrs ... but that function is already globally exposed.

I don't much care for re-calling build_base_rel_tlists to add extra
Vars to the appropriate relations; 99% of the work it does will be
wasted, and with enough child rels you could run into an O(N^2)
problem.  Maybe you could call add_vars_to_targetlist directly,
since you know exactly what Vars you're adding?

What is the point of moving the calculation of all_baserels?  The earlier
you construct that, the more likelihood that code will have to be written
to modify it (like, say, what you had to put into
create_inherited_target_child_root), and I do not see anything in this
patch series that needs it to be available earlier.

The business with translated_exprs and child_target_exprs in
set_inherited_target_rel_sizes seems to be dead code --- nothing is done
with the list.  Should that be updating childrel->reltarget?  Or is that
now done elsewhere, and if so, why isn't the elsewhere also handling
childrel->joininfo?

+         * Set a non-zero value here to cope with the caller's requirement
+         * that non-dummy relations are actually not empty.  We don't try to

What caller is that?  Perhaps we should change that rather than inventing
a bogus value here?

Couldn't inh_target_child_rels list be removed in favor of looking
at the relid fields of the inh_target_child_path_rels entries?
Having to keep those two lists in sync seems messy.

If you're adding fields to PlannerInfo (or pretty much any other
planner data structure) you should update outfuncs.c to print them
if feasible.  Also, please avoid "add new fields at the end" syndrome.
Put them where they logically belong.  For example, if
inh_target_child_roots has to be the same length as simple_rel_array,
it's not just confusing for it not to be near that field, it's
outright dangerous: it increases the risk that somebody will forget
to manipulate both fields.

			regards, tom lane




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-02-19 00:49  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 1 reply; 265+ messages in thread

From: Imai, Yoshikazu @ 2019-02-19 00:49 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Amit Langote <amitlangote09@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On Mon, Feb 18, 2019 at 5:28 PM, Tom Lane wrote:
> Frankly, that code is just horrid.  Having a function with side effects
> in an if-test is questionable at the best of times, and having it be the
> second of three conditions (which the third condition silently depends
> on) is unreadable and unmaintainable.

When I reviewed this, I thought there are no problems in the codes, but I googled what Tom pointed out[1], read it and I was ashamed of my ignorance.

> I think the existing code here is considerably cleaner than what this
> patch proposes.
> 
> I suppose you are doing this because you intend to jam some additional
> cleanup code into the successfully-pruned-it code path, but if said code
> is really too bulky to have multiple copies of, couldn't you put it into
> a subroutine?

ISTM the 0004 patch eventually removes these codes from multiple places (set_append_rel_size and set_inherited_target_rel_sizes) so we might be better to not be struggling here?

[1] https://www.teamten.com/lawrence/programming/keep-if-clauses-side-effect-free.html

--
Yoshikazu Imai




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-19 00:54  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 0 replies; 265+ messages in thread

From: Amit Langote @ 2019-02-19 00:54 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Amit Langote <amitlangote09@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Thanks for looking.

On 2019/02/19 2:27, Tom Lane wrote:
> Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> writes:
>> [ v22 patch set ]
> 
> I started to look at this, and immediately choked on the 0001 patch:
> 
>         if (childpruned ||
>             !apply_child_basequals(root, rel, childrel, childRTE, appinfo) ||
>             relation_excluded_by_constraints(root, childrel, childRTE))
>         {
> 
> Frankly, that code is just horrid.  Having a function with side effects
> in an if-test is questionable at the best of times, and having it be
> the second of three conditions (which the third condition silently depends
> on) is unreadable and unmaintainable.
> 
> I think the existing code here is considerably cleaner than what this
> patch proposes.

OK, I think we can just skip this patch.

> I suppose you are doing this because you intend to jam some additional
> cleanup code into the successfully-pruned-it code path, but if said
> code is really too bulky to have multiple copies of, couldn't you
> put it into a subroutine?

Actually, one of the later patches (lazy creation of partition RTEs)
*replaces* the the above code block by:

if (IS_DUMMY_REL(childrel))
    continue;

because with that patch, the step that prunes/excludes children will occur
earlier than set_rel_size / set_append_rel_size.  For pruned children,
there won't RTE/RelOptInfo/AppendRelInfo to begin with.  Children that
survive partition pruning but get excluded due to contradictory quals
(apply_child_basequals returning false) or constraint exclusion will be
marked dummy before even getting to set_append_rel_size.

I'll adjust the patches accordingly.

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-19 01:36  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  0 siblings, 0 replies; 265+ messages in thread

From: Tom Lane @ 2019-02-19 01:36 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; David Rowley <david.rowley@2ndquadrant.com>; Amit Langote <amitlangote09@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

"Imai, Yoshikazu" <imai.yoshikazu@jp.fujitsu.com> writes:
> ISTM the 0004 patch eventually removes these codes from multiple places (set_append_rel_size and set_inherited_target_rel_sizes) so we might be better to not be struggling here?

Yeah, Amit just pointed that out (and I'd not read 0004 before reacting to
0001).  If the final state of the code isn't going to look like this, then
whether the intermediate state is good style becomes far less important.
Still, maybe it'd be better to drop the 0001 patch and absorb its effects
into the later patch that makes that if-test go away entirely.

			regards, tom lane




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-19 10:42  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 1 reply; 265+ messages in thread

From: Amit Langote @ 2019-02-19 10:42 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Amit Langote <amitlangote09@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Thanks for the review.

On 2019/02/19 4:42, Tom Lane wrote:
> Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> writes:
>> [ v22 patch set ]
> 
> I did some review of the 0002 patch.  I like the general idea,
> but there are a lot of details I don't much like.
> 
> Probably the biggest complaint is that I don't like what you did to the
> API of grouping_planner(): it's ugly and unprincipled.  Considering that
> only a tiny fraction of what grouping_planner does is actually relevant to
> inherited UPDATES/DELETEs, maybe we should factor that part out --- or,
> heavens, just duplicate some code --- so that inheritance_planner doesn't
> need to call grouping_planner at all.  (I see that you have another patch
> in the queue that proposes to fix this through a rather massive
> refactoring of grouping_planner, but I do not think I like that approach.
> grouping_planner does perhaps need refactoring, but I don't want to drive
> such work off an arbitrary-and-dubious requirement that it shouldn't call
> query_planner.)

OK, modified the patch to leave grouping_planner unchanged (except got rid
of inheritance_update argument).  Now inheritance_planner directly
modifies the paths produced by query_planner (per child target relation
that is), so that they have a PathTarget suitable for producing query's
top-level targetlist.  I refactored portion of
apply_scanjoin_target_to_paths() that does that work and
inheritance_planner calls it directly.

> Another point is that I don't like this division of labor between
> equivclass.c and appendinfo.c.  I don't like exposing add_eq_member
> globally --- that's just an invitation for code outside equivclass.c to
> break the fundamental invariants of ECs --- and I also think you've taught
> appendinfo.c far more than it ought to know about the innards of ECs.
> I'd suggest putting all of that logic into equivclass.c, with a name along
> the lines of translate_equivalence_class_to_child.  That would reverse the
> dependency, in that equivclass.c would now need to call
> adjust_appendrel_attrs ... but that function is already globally exposed.

That makes sense.  I've tried implementing EC translation to occur this
way in the updated patch.

> I don't much care for re-calling build_base_rel_tlists to add extra
> Vars to the appropriate relations; 99% of the work it does will be
> wasted, and with enough child rels you could run into an O(N^2)
> problem.  Maybe you could call add_vars_to_targetlist directly,
> since you know exactly what Vars you're adding?

Assuming you're talking about the build_base_rel_tlists() call in
create_inherited_target_child_root(), it's necessary because *all* tlist
Vars are new, because the targetlist has just been translated at that
point.  But maybe I missed your point?

By the way, later patch in the series will cause partition pruning to
occur before these child PlannerInfos are generated, so
create_inherited_target_child_root will be called only as many times as
there are un-pruned child target relations.

> What is the point of moving the calculation of all_baserels?  The earlier
> you construct that, the more likelihood that code will have to be written
> to modify it (like, say, what you had to put into
> create_inherited_target_child_root), and I do not see anything in this
> patch series that needs it to be available earlier.

all_baserels needs to be built in original PlannerInfo before child
PlannerInfos are constructed, so that they can simply copy it and have the
parent target baserel RT index in it replaced by child target baserel RT
index.  set_inherited_target_rel_sizes/pathlists use child PlannerInfos,
so all_baserels must be set in them just like it is in the original
PlannerInfo.

> The business with translated_exprs and child_target_exprs in
> set_inherited_target_rel_sizes seems to be dead code --- nothing is done
> with the list.  Should that be updating childrel->reltarget?  Or is that
> now done elsewhere, and if so, why isn't the elsewhere also handling
> childrel->joininfo?

Actually, child_target_exprs simply refers to childrel->reltarget->exprs,
so modifying it modifies the latter too, but I've found that confusing
myself.  So, I have removed it.

childrel->reltarget->exprs would only contain the targetlist Vars at this
point (added by build_base_rel_tlists called by
create_inherited_target_child_root), but translated_exprs will also
contain Vars that are referenced in WHERE clauses, which have not been
added to childrel->reltarget->exprs yet.  That's what's getting added to
childrel->reltarget in this code.

> +         * Set a non-zero value here to cope with the caller's requirement
> +         * that non-dummy relations are actually not empty.  We don't try to
> 
> What caller is that?  Perhaps we should change that rather than inventing
> a bogus value here?

OK, modified relevant Asserts in the callers.  One was set_rel_size and
other set_inherited_target_rel_sizes itself (may be called recursively).

> Couldn't inh_target_child_rels list be removed in favor of looking
> at the relid fields of the inh_target_child_path_rels entries?
> Having to keep those two lists in sync seems messy.

Don't like having two lists either, but inh_target_child_path_rels entries
can be RELOPT_JOINREL, so relid can be 0.

> If you're adding fields to PlannerInfo (or pretty much any other
> planner data structure) you should update outfuncs.c to print them
> if feasible.  Also, please avoid "add new fields at the end" syndrome.
> Put them where they logically belong.  For example, if
> inh_target_child_roots has to be the same length as simple_rel_array,
> it's not just confusing for it not to be near that field, it's
> outright dangerous: it increases the risk that somebody will forget
> to manipulate both fields.

I've moved these fields around in the struct definition.  Also, I've added
unexpanded_tlist and inh_target_child_rels to _outPlannerInfo.

Attached updated patches.  0001 that I'd previously posted is no longer
included, as I said in the other email.

Thanks,
Amit

Attachments: download all (5 files, .tar.gz)

0001-Overhaul-inheritance-update-delete-planning.patch (text/plain, 69.4K) download From 81632e4088bf1b711c80ff07762ac72cd74d9327 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 8 Feb 2019 16:16:38 +0900 Subject: [PATCH 1/5] Overhaul inheritance update/delete planning With the current method, inheritance_planner, which handles the planning for update/delete commands targeting inheritance trees, repeatedly calls query_planner for each child table. That's inefficient for two reasons: 1. partprune.c cannot be used to perform partition pruning, because it can only be invoked from query_planner. With the current approach, query_planner only sees the individual partitions in the query tree it receives, not the parent partitioned table. This leaves each partition to be pruned using constraint exclusion. (constraint exclusion cannon prune hash partitions which means UPDATE/DELETE don't support hash partition pruning.) 2. Repeated invocation of query_planner results in allocating large amounts of memory, especially if there are many child tables. Also, it involves repeatedly performing the same processing, such as jointree processing. This commit addresses both of the issues by modifying query_planner to handle the inheritance target case directly. Instead of having the caller create separate PlannerInfo's for individual child target relations and pass them to query_planner (actually, via grouping_planner), this teaches query_planner itself to create child PlannerInfo's from the informaton in the original parent PlannerInfo. It creates them *after* it has generated enough state in the original, PlannerInfo, such as RelOptInfos for various relations, EquivalenceClasses after processing the join tree, and *before* generating the scan and join paths. Scan paths are generated normally, except that Paths of target children are not "appended". Join paths are then generated separately for each child target relation, after replacing the parent relation references in the original join tree with the child relation references. Join paths corresponding to each child target are remembered for further processing by stuffing stuffing the joinrel RelOptInfos in a list in the original PlannerInfo. So, inheritance_planner now calls query_planner just once at the beginning with the original unmodified query, which takes care of generating top-level join rels for the child queries as described above. Once join paths have been created for all unpruned child target relations, back in inheritance_planner, the aforementioned paths are adjusted so that they produce the query's top-level target list that is expanded according to a given child relation's descriptor. This removes some existing code in inheritance_planner that dealt with any subquery RTEs in the query. The rationale of that code was that the subquery RTEs may change during each iteration of planning (that is, for different children), so different iterations better use different copies of those RTEs. That was handled by making fresh copies of those RTEs for each iteration of planning which were appended to the range table, accompanied by modifying all expressions and auxiliary structures that referenced the original subquery RTEs to instead reference the copies (that is, change the varnos). This copying meant we would end up adding S * N new entries to the original range table by the time we got to the last unpruned child, where S is the number of subquery RTEs in the original query and N the number of unpruned children. Since with the new code we perform planning just once, I think we don't need this special handling. Actually, there is a regression test output change due to no longer having copies of subquery RTEs (see the diff of partition_join.out file.) Regression test output change in partition_join.out is due to the fact that we no longer create duplicates of subquery RTEs in child range tables. --- doc/src/sgml/ddl.sgml | 17 +- src/backend/nodes/outfuncs.c | 2 + src/backend/optimizer/path/allpaths.c | 449 ++++++++++++++++++++-- src/backend/optimizer/path/equivclass.c | 160 ++++++++ src/backend/optimizer/plan/planmain.c | 41 +- src/backend/optimizer/plan/planner.c | 550 ++++++++++----------------- src/backend/optimizer/util/appendinfo.c | 30 +- src/backend/optimizer/util/inherit.c | 127 +++++++ src/include/nodes/pathnodes.h | 30 ++ src/include/optimizer/inherit.h | 1 + src/include/optimizer/paths.h | 2 + src/test/regress/expected/partition_join.out | 4 +- 12 files changed, 1002 insertions(+), 411 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 8314fce78f..1da28357ef 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -4439,24 +4439,11 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <note> <para> - Currently, pruning of partitions during the planning of an - <command>UPDATE</command> or <command>DELETE</command> command is - implemented using the constraint exclusion method (however, it is - controlled by the <literal>enable_partition_pruning</literal> rather than - <literal>constraint_exclusion</literal>) &mdash; see the following section - for details and caveats that apply. - </para> - - <para> Execution-time partition pruning currently only occurs for the <literal>Append</literal> and <literal>MergeAppend</literal> node types. It is not yet implemented for the <literal>ModifyTable</literal> node - type. - </para> - - <para> - Both of these behaviors are likely to be changed in a future release - of <productname>PostgreSQL</productname>. + type, but that is likely to be changed in a future release of + <productname>PostgreSQL</productname>. </para> </note> </sect2> diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 65302fe65b..c5c08efac1 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2203,6 +2203,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_NODE_FIELD(distinct_pathkeys); WRITE_NODE_FIELD(sort_pathkeys); WRITE_NODE_FIELD(processed_tlist); + WRITE_NODE_FIELD(unexpanded_tlist); WRITE_NODE_FIELD(minmax_aggs); WRITE_FLOAT_FIELD(total_table_pages, "%.0f"); WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); @@ -2218,6 +2219,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_BITMAPSET_FIELD(curOuterRels); WRITE_NODE_FIELD(curOuterParams); WRITE_BOOL_FIELD(partColsUpdated); + WRITE_NODE_FIELD(inh_target_child_rels); } static void diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 0debac75c6..0a4729e646 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -39,6 +39,7 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" @@ -92,8 +93,12 @@ static void set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static void set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); +static void set_inherited_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte); static void set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); +static void set_inherited_target_rel_pathlists(PlannerInfo *root, + RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel, @@ -122,6 +127,8 @@ static void set_result_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); +static RelOptInfo *inheritance_make_rel_from_joinlist(PlannerInfo *root, + List *joinlist); static bool subquery_is_pushdown_safe(Query *subquery, Query *topquery, pushdown_safety_info *safetyInfo); static bool recurse_pushdown_safe(Node *setOp, Query *topquery, @@ -155,27 +162,6 @@ make_one_rel(PlannerInfo *root, List *joinlist) Index rti; double total_pages; - /* - * Construct the all_baserels Relids set. - */ - root->all_baserels = NULL; - for (rti = 1; rti < root->simple_rel_array_size; rti++) - { - RelOptInfo *brel = root->simple_rel_array[rti]; - - /* there may be empty slots corresponding to non-baserel RTEs */ - if (brel == NULL) - continue; - - Assert(brel->relid == rti); /* sanity check on array */ - - /* ignore RTEs that are "other rels" */ - if (brel->reloptkind != RELOPT_BASEREL) - continue; - - root->all_baserels = bms_add_member(root->all_baserels, brel->relid); - } - /* Mark base rels as to whether we care about fast-start plans */ set_base_rel_consider_startup(root); @@ -223,13 +209,35 @@ make_one_rel(PlannerInfo *root, List *joinlist) /* * Generate access paths for the entire join tree. + * + * For UPDATE/DELETE on an inheritance parent, join paths should be + * generated for each child result rel separately. */ - rel = make_rel_from_joinlist(root, joinlist); + if (root->parse->resultRelation > 0 && + root->simple_rte_array[root->parse->resultRelation]->inh) + { + /* + * RelOptInfo corresponding to the query's original target relation + * is returned. Join paths (if any) are attached to child joinrels, + * not this rel. Also, this rel's sole path (ModifyTable) will be set + * by inheritance_planner later, so we can't check its paths yet. + */ + rel = inheritance_make_rel_from_joinlist(root, joinlist); + } + else + { + rel = make_rel_from_joinlist(root, joinlist); - /* - * The result should join all and only the query's base rels. - */ - Assert(bms_equal(rel->relids, root->all_baserels)); + /* + * The result should join all and only the query's base rels. + */ + Assert(bms_equal(rel->relids, root->all_baserels)); + + /* Check that we got at least one usable path */ + if (!rel || !rel->cheapest_total_path || + rel->cheapest_total_path->param_info != NULL) + elog(ERROR, "failed to construct the join relation"); + } return rel; } @@ -361,6 +369,8 @@ static void set_rel_size(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { + bool inherited_update = false; + if (rel->reloptkind == RELOPT_BASEREL && relation_excluded_by_constraints(root, rel, rte)) { @@ -379,8 +389,18 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_size(root, rel, rti, rte); + /* + * If it's a target relation, set the sizes of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (rti == root->parse->resultRelation) + { + inherited_update = true; + set_inherited_target_rel_sizes(root, rel, rti, rte); + } + else + set_append_rel_size(root, rel, rti, rte); } else { @@ -457,8 +477,9 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, /* * We insist that all non-dummy rels have a nonzero rowcount estimate. + * An exception is target relations that are inheritance parents. */ - Assert(rel->rows > 0 || IS_DUMMY_REL(rel)); + Assert(rel->rows > 0 || IS_DUMMY_REL(rel) || inherited_update); } /* @@ -469,14 +490,26 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) { + bool inherited_update = false; + if (IS_DUMMY_REL(rel)) { /* We already proved the relation empty, so nothing more to do */ } else if (rte->inh) { - /* It's an "append relation", process accordingly */ - set_append_rel_pathlist(root, rel, rti, rte); + /* + * If it's a target relation, set the pathlists of children instead. + * Otherwise, we'll append the outputs of children, so process it as + * an "append relation". + */ + if (rti == root->parse->resultRelation) + { + inherited_update = true; + set_inherited_target_rel_pathlists(root, rel, rti, rte); + } + else + set_append_rel_pathlist(root, rel, rti, rte); } else { @@ -530,6 +563,19 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, } /* + * If the relation is an inheritance parent and the query's result + * relation, no paths have been generated for it yet, so skip the following + * steps. + */ + if (inherited_update) + { +#ifdef OPTIMIZER_DEBUG + debug_print_rel(root, rel); +#endif + return; + } + + /* * Allow a plugin to editorialize on the set of Paths for this base * relation. It could add new paths (such as CustomPaths) by calling * add_path(), or add_partial_path() if parallel aware. It could also @@ -924,6 +970,207 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) } /* + * set_inherited_target_rel_sizes + * Set size estimates for the child target relations + * + * The passed-in rel represents the target relation of the query that is + * known to have inheritance children. This is very much like + * set_append_rel_size, except it doesn't set the size estimates for the + * passed-in rel itself, because we don't need to "append" the children + * in this case. + */ +static void +set_inherited_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + bool has_live_children = false; + ListCell *l; + Relids live_children = NULL; + bool did_pruning = false; + + /* Guard against stack overflow due to overly deep inheritance tree. */ + check_stack_depth(); + + Assert(IS_SIMPLE_REL(rel)); + + /* + * If the partitioned relation has any baserestrictinfo quals then we + * attempt to use these quals to prune away partitions that cannot + * possibly contain any tuples matching these quals. In this case we'll + * store the relids of all partitions which could possibly contain a + * matching tuple, and skip anything else in the loop below. + */ + if (enable_partition_pruning && + rte->relkind == RELKIND_PARTITIONED_TABLE && + rel->baserestrictinfo != NIL) + { + live_children = prune_append_rel_partitions(rel); + did_pruning = true; + } + + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + PlannerInfo *subroot; + ListCell *lc; + List *translated_exprs; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + + /* + * The child rel's RelOptInfo was already created during + * add_base_rels_to_query. + */ + childrel = find_base_rel(root, childRTindex); + Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); + + if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) + { + /* This partition was pruned; skip it. */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * 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 + * baserestrictinfo quals. + */ + if (!apply_child_basequals(root, rel, childrel, childRTE, appinfo)) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + if (relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* + * This child need not be scanned, so we can omit it from the + * appendrel. + */ + set_dummy_rel_pathlist(childrel); + continue; + } + + /* + * Add missing Vars to child's reltarget. + * + * create_inherited_target_child_root() would've added only those that + * are needed due to being referenced in the top-level tlist (or ones + * that preprocess_targetlist thinks are needed to be in the tlist.) + * We may need other attributes such as those contained in WHERE + * clauses, which are already computed for the parent during + * deconstruct_jointree processing of the original query (child's + * query never goes through deconstruct_jointree.) + */ + translated_exprs = (List *) + adjust_appendrel_attrs(root, + (Node *) rel->reltarget->exprs, + 1, &appinfo); + foreach(lc, translated_exprs) + { + Expr *expr = lfirst(lc); + + if (!list_member(childrel->reltarget->exprs, expr)) + childrel->reltarget->exprs = + lappend(childrel->reltarget->exprs, expr); + } + + childrel->has_eclass_joins = rel->has_eclass_joins; + + subroot = root->inh_target_child_roots[childRTindex]; + + /* Translate join quals. */ + childrel->joininfo = (List *) + adjust_appendrel_attrs(subroot, + (Node *) rel->joininfo, + 1, &appinfo); + + /* + * Compute the child's size using possibly modified subroot. + */ + set_rel_size(subroot, childrel, childRTindex, childRTE); + + /* + * If the child is a partitioned table it may have been marked as a + * dummy rel from having all its partitions pruned. + */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* We have at least one live child. */ + has_live_children = true; + + Assert(childrel->rows > 0 || childRTE->inh); + } + + /* + * All children were excluded by constraints, so mark the relation + * as dummy. We must do this in this phase so that the rel's + * dummy-ness is visible when we generate paths for other rels. + */ + if (!has_live_children) + set_dummy_rel_pathlist(rel); +} + +/* + * set_inherited_target_rel_pathlists + * Build access paths for the child target relations + * + * Similar to set_append_rel_pathlist, except that we build paths of the + * children, but don't build an Append path. + */ +static void +set_inherited_target_rel_pathlists(PlannerInfo *root, RelOptInfo *rel, + Index rti, RangeTblEntry *rte) +{ + int parentRTindex = rti; + ListCell *l; + + /* Generate access paths for each of the children of passed-in rel */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); + int childRTindex; + RangeTblEntry *childRTE; + RelOptInfo *childrel; + PlannerInfo *subroot; + + /* append_rel_list contains all append rels; ignore others */ + if (appinfo->parent_relid != parentRTindex) + continue; + + /* Re-locate the child RTE and RelOptInfo */ + childRTindex = appinfo->child_relid; + childRTE = root->simple_rte_array[childRTindex]; + childrel = root->simple_rel_array[childRTindex]; + subroot = root->inh_target_child_roots[childRTindex]; + /* Transfer the value from main root to subroot. */ + subroot->total_table_pages = root->total_table_pages; + + /* + * Compute the child's access paths. + */ + set_rel_pathlist(subroot, childrel, childRTindex, childRTE); + } +} + +/* * set_append_rel_size * Set size estimates for a simple "append relation" * @@ -2566,6 +2813,146 @@ generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool override_rows) } /* + * inheritance_make_rel_from_joinlist + * Perform join planning for all non-dummy leaf inheritance children + * in their role as an UPDATE/DELETE query's target relation + * + * If a child relation is a partitioned table, its children are processed in + * turn by recursively calling this function. + */ +static RelOptInfo * +inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) +{ + Index resultRelation = root->parse->resultRelation; + RelOptInfo *resultrel; + ListCell *lc; + + /* For UPDATE/DELETE queries, the top parent can only ever be a table. */ + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + Assert(planner_rt_fetch(resultRelation, root)->rtekind == RTE_RELATION); + resultrel = find_base_rel(root, resultRelation); + + /* Nothing to do. */ + if (IS_DUMMY_REL(resultrel)) + return resultrel; + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + PlannerInfo *subroot; + RelOptInfo *childrel; + RelOptInfo *childjoinrel; + List *translated_joinlist; + + if (appinfo->parent_relid != resultRelation) + continue; + + childrel = find_base_rel(root, appinfo->child_relid); + + /* Ignore excluded/pruned children. */ + if (IS_DUMMY_REL(childrel)) + continue; + + /* Add this child. */ + root->inh_target_child_rels = lappend_int(root->inh_target_child_rels, + appinfo->child_relid); + + /* Perform join planning with child subroot. */ + subroot = root->inh_target_child_roots[appinfo->child_relid]; + Assert(subroot->parse->resultRelation > 0); + + /* + * Modify joinlist such that relations joined to the top parent rel + * appear to be joined to the child rel instead. Do the same for + * any SpecialJoinInfo structs. + */ + translated_joinlist = (List *) + adjust_appendrel_attrs(subroot, + (Node *) joinlist, + 1, &appinfo); + subroot->join_info_list = (List *) + adjust_appendrel_attrs(subroot, + (Node *) root->join_info_list, + 1, &appinfo); + + /* + * Sub-partitioned tables have to be processed recursively using the + * translated subroot as the parent, because AppendRelInfos link + * sub-partitions to their immediate parents, not the root partitioned + * table. + */ + if (childrel->part_scheme != NULL) + { + /* + * inheritance_make_rel_from_joinlist() return the target relation + * RelOptInfo. + */ + childrel = + inheritance_make_rel_from_joinlist(subroot, + translated_joinlist); + + /* + * Add this child relation as a placeholder in the parent root's + * inh_target_child_path_rels so that inheritance_planner sees + * same number of entries in it as inh_target_child_rels. + */ + root->inh_target_child_path_rels = + lappend(root->inh_target_child_path_rels, childrel); + + /* + * Also propagate this child's own children into the parent's + * list. + */ + if (subroot->inh_target_child_rels != NIL) + { + root->inh_target_child_rels = + list_concat(root->inh_target_child_rels, + subroot->inh_target_child_rels); + root->inh_target_child_path_rels = + list_concat(root->inh_target_child_path_rels, + subroot->inh_target_child_path_rels); + } + continue; + } + + /* + * Since we added the child rel directly into the join tree, we must + * modify it to be a "base" rel instead of an "other" rel, which the + * join planning code expects the relations being joined to be. + */ + childrel->reloptkind = RELOPT_BASEREL; + + Assert(subroot->join_rel_list == NIL); + Assert(subroot->join_rel_hash == NULL); + + /* Perform join planning and save the resulting RelOptInfo. */ + childjoinrel = make_rel_from_joinlist(subroot, translated_joinlist); + + /* + * The following implements essentially the same Assert as in + * make_one_rel, our caller. + */ + Assert(bms_equal(childjoinrel->relids, subroot->all_baserels)); + + /* Check that we got at least one usable path */ + if (!childjoinrel || !childjoinrel->cheapest_total_path || + childjoinrel->cheapest_total_path->param_info != NULL) + elog(ERROR, "failed to construct the child join relation"); + + /* + * Remember the paths of this child target rel. inheritance_planner + * perform remaining steps necessary fot these paths to produce the + * desired targetlist. + */ + root->inh_target_child_path_rels = + lappend(root->inh_target_child_path_rels, childjoinrel); + } + + return resultrel; +} + +/* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. * diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 23792508b7..11244fc57d 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -31,6 +31,11 @@ #include "optimizer/restrictinfo.h" #include "utils/lsyscache.h" +typedef struct translate_eq_classes_context +{ + PlannerInfo *root; + AppendRelInfo *appinfo; +} translate_eq_classes_context; static EquivalenceMember *add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids, Relids nullable_relids, @@ -64,6 +69,10 @@ static bool reconsider_outer_join_clause(PlannerInfo *root, bool outer_on_left); static bool reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo); +static EquivalenceClass * adjust_inherited_eq_class(PlannerInfo *root, + EquivalenceClass *new_ec, AppendRelInfo *appinfo); +static Node *translate_eq_classes_mutator(Node *node, + translate_eq_classes_context *context); /* @@ -2185,6 +2194,157 @@ add_child_rel_equivalences(PlannerInfo *root, } } +/* + * translate_eq_classes_for_child + * This creates a copy of the list of EquivalenceClasses, each of + * which is translated with the provided AppendRelInfo + * + * Whereas add_child_rel_equivalences simply adds child Vars to a given + * EquivalenceClasses if it contains the Vars of a given parent relation, + * this creates a whole new EquivalenceClasses copied from the original one + * and if it contains Vars of the parent relation, they're *replaced* with + * the corresponding child Vars. + */ +List * +translate_eq_classes_for_child(PlannerInfo *root, List *eq_classes, + AppendRelInfo *appinfo) +{ + translate_eq_classes_context context; + + context.root = root; + context.appinfo = appinfo; + + return (List *) translate_eq_classes_mutator((Node *) eq_classes, + &context); +} + +static Node * +translate_eq_classes_mutator(Node *node, + translate_eq_classes_context *context) +{ + if (node == NULL) + return NULL; + + if (IsA(node, EquivalenceClass)) + { + EquivalenceClass *ec = (EquivalenceClass *) node; + EquivalenceClass *new_ec = makeNode(EquivalenceClass); + + /* + * First memcpy the existing EC to the new EC, which creates shallow + * copies of all the members and then make copies of members that + * we might change below. + * + * XXX comment in _copyPathKey says it's OK to recycle EC + * pointers, but as long as we do the whole planning for a + * given child using a given root, copying ECs like this + * shouldn't be a problem. Maybe, the following code could + * be in _copyEquivalenceClass()? + */ + memcpy(new_ec, ec, sizeof(EquivalenceClass)); + new_ec->ec_opfamilies = list_copy(ec->ec_opfamilies); + new_ec->ec_sources = list_copy(ec->ec_sources); + new_ec->ec_derives = list_copy(ec->ec_derives); + new_ec->ec_relids = bms_copy(ec->ec_relids); + new_ec->ec_members = list_copy(ec->ec_members); + + /* + * No point in searching if parent rel not mentioned in eclass; but we + * can't tell that for sure if parent rel is itself a child. + */ + if (bms_is_member(context->appinfo->parent_relid, ec->ec_relids)) + new_ec = adjust_inherited_eq_class(context->root, new_ec, + context->appinfo); + + return (Node *) new_ec; + } + + return expression_tree_mutator(node, translate_eq_classes_mutator, + (void *) context); +} + +/* + * adjust_inherited_eq_class + * This translates members of EquivalenceClass such that references to + * an inheritance parent relation are replaced by the references to + * a given child relation + * + * This performs in-place updates of the members of the input struct, so the + * callers should've made a copy of the original EC first. + */ +static EquivalenceClass * +adjust_inherited_eq_class(PlannerInfo *root, EquivalenceClass *new_ec, + AppendRelInfo *appinfo) +{ + ListCell *lc; + RelOptInfo *parent_rel = find_base_rel(root, appinfo->parent_relid), + *child_rel = find_base_rel(root, appinfo->child_relid); + + /* + * Check if this EC contains an expression referencing the parent + * relation, translate it to child, and store it in place of + * the original parent expression. + */ + foreach(lc, new_ec->ec_members) + { + EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc); + + if (cur_em->em_is_const) + continue; /* ignore consts here */ + + /* Does it reference parent_rel? */ + if (bms_overlap(cur_em->em_relids, parent_rel->relids)) + { + /* Yes, generate transformed child version */ + Expr *new_expr; + Relids new_relids; + Relids new_nullable_relids; + + new_expr = (Expr *) + adjust_appendrel_attrs(root, + (Node *) cur_em->em_expr, + 1, &appinfo); + + /* + * Transform em_relids to match. Note we do *not* do + * pull_varnos(child_expr) here, as for example the + * transformation might have substituted a constant, but we + * don't want the child member to be marked as constant. + */ + new_relids = bms_difference(cur_em->em_relids, parent_rel->relids); + new_relids = bms_add_members(new_relids, child_rel->relids); + + /* + * And likewise for nullable_relids. Note this code assumes + * parent and child relids are singletons. + */ + new_nullable_relids = cur_em->em_nullable_relids; + if (bms_overlap(new_nullable_relids, parent_rel->relids)) + { + new_nullable_relids = bms_difference(new_nullable_relids, + parent_rel->relids); + new_nullable_relids = bms_add_members(new_nullable_relids, + child_rel->relids); + } + + /* + * The new expression simply replaces the old parent one, and + * em_is_child is set to true so that it's recognized as such + * during child planning. + */ + lfirst(lc) = add_eq_member(new_ec, new_expr, + new_relids, new_nullable_relids, + false, cur_em->em_datatype); + } + } + + /* Now fix up EC's relids set. */ + new_ec->ec_relids = bms_del_members(new_ec->ec_relids, parent_rel->relids); + new_ec->ec_relids = bms_add_members(new_ec->ec_relids, child_rel->relids); + + return new_ec; +} + /* * generate_implied_equalities_for_column diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 3cedd01c98..c7f2323852 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -20,6 +20,7 @@ */ #include "postgres.h" +#include "nodes/pathnodes.h" #include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/inherit.h" @@ -29,6 +30,7 @@ #include "optimizer/paths.h" #include "optimizer/placeholder.h" #include "optimizer/planmain.h" +#include "optimizer/prep.h" /* @@ -60,6 +62,7 @@ query_planner(PlannerInfo *root, List *tlist, Query *parse = root->parse; List *joinlist; RelOptInfo *final_rel; + Index rti; /* * Init planner lists to empty. @@ -260,14 +263,42 @@ query_planner(PlannerInfo *root, List *tlist, extract_restriction_or_clauses(root); /* + * Construct the all_baserels Relids set. + */ + root->all_baserels = NULL; + for (rti = 1; rti < root->simple_rel_array_size; rti++) + { + RelOptInfo *brel = root->simple_rel_array[rti]; + + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + Assert(brel->relid == rti); /* sanity check on array */ + + /* ignore RTEs that are "other rels" */ + if (brel->reloptkind != RELOPT_BASEREL) + continue; + + root->all_baserels = bms_add_member(root->all_baserels, brel->relid); + } + + /* + * Add child subroots needed during planning for individual child targets. + */ + if (parse->resultRelation && + root->simple_rte_array[parse->resultRelation]->inh) + { + root->inh_target_child_roots = (PlannerInfo **) + palloc0(root->simple_rel_array_size * + sizeof(PlannerInfo *)); + add_inherited_target_child_roots(root); + } + + /* * Ready to do the primary planning. */ final_rel = make_one_rel(root, joinlist); - /* Check that we got at least one usable path */ - if (!final_rel || !final_rel->cheapest_total_path || - final_rel->cheapest_total_path->param_info != NULL) - elog(ERROR, "failed to construct the join relation"); - return final_rel; } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 1a58d733fa..cf64688e76 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -130,8 +130,7 @@ typedef struct static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static void inheritance_planner(PlannerInfo *root); -static void grouping_planner(PlannerInfo *root, bool inheritance_update, - double tuple_fraction); +static void grouping_planner(PlannerInfo *root, double tuple_fraction); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, int *tleref_to_colnum_map); @@ -241,6 +240,10 @@ static void apply_scanjoin_target_to_paths(PlannerInfo *root, List *scanjoin_targets_contain_srfs, bool scanjoin_target_parallel_safe, bool tlist_same_exprs); +static void apply_scanjoin_target_to_paths_guts(PlannerInfo *root, + RelOptInfo *rel, + PathTarget *scanjoin_target, + bool tlist_same_exprs); static void create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *grouped_rel, @@ -997,7 +1000,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rt_fetch(parse->resultRelation, parse->rtable)->inh) inheritance_planner(root); else - grouping_planner(root, false, tuple_fraction); + grouping_planner(root, tuple_fraction); /* * Capture the set of outer-level param IDs we have access to, for use in @@ -1173,13 +1176,38 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr) * inheritance set. * * We have to handle this case differently from cases where a source relation - * is an inheritance set. Source inheritance is expanded at the bottom of the - * plan tree (see allpaths.c), but target inheritance has to be expanded at - * the top. The reason is that for UPDATE, each target relation needs a - * different targetlist matching its own column set. Fortunately, + * is an inheritance set, where outputs of all the children are combined and + * combined output consumed using the source relation's (parent's) column set. + * For UPDATE, each target relation, where the query's output will go, needs + * a different targetlist matching its own column set. So, we will need to + * translate the query such that it produces the desired output required by + * each of the child target relations and plan each separately. Fortunately, * the UPDATE/DELETE target can never be the nullable side of an outer join, * so it's OK to generate the plan this way. * + * Planning steps that need to be performed separately for each child and + * any preliminary processing needed for that are handled by the following + * sub-routines of query_planner: + * + * add_inherited_target_child_roots - this creates copies of PlannerInfo for + * each child after query_planner has finished processing the join tree, with + * translated copies of parsetree, eq_classes, etc. + * + * set_inherited_target_rel_sizes - this sets size estimates for child + * relations + * + * set_inherited_target_rel_pathlists - this creates Paths for scanning + * individual child relations + * + * inheritance_make_rel_from_joinlist - this translates the jointree, + * replacing the target relation in the original jointree (the root parent) + * by individual child target relations and performs join planning on the + * resulting join tree, saving the RelOptInfos of resulting join relations + * into the top-level PlannerInfo + * + * Finally, here we adjust the Paths of individual children do that they + * produces the desired targetlist. + * * Returns nothing; the useful output is in the Paths we attach to * the (UPPERREL_FINAL, NULL) upperrel stored in *root. * @@ -1191,14 +1219,8 @@ inheritance_planner(PlannerInfo *root) { Query *parse = root->parse; int top_parentRTindex = parse->resultRelation; - Bitmapset *subqueryRTindexes; - Bitmapset *modifiableARIindexes; int nominalRelation = -1; Index rootRelation = 0; - List *final_rtable = NIL; - int save_rel_array_size = 0; - RelOptInfo **save_rel_array = NULL; - AppendRelInfo **save_append_rel_array = NULL; List *subpaths = NIL; List *subroots = NIL; List *resultRelations = NIL; @@ -1206,70 +1228,52 @@ inheritance_planner(PlannerInfo *root) List *returningLists = NIL; List *rowMarks; RelOptInfo *final_rel; - ListCell *lc; - Index rti; + ListCell *lc1, + *lc2; RangeTblEntry *parent_rte; - PlannerInfo *parent_root; - Query *parent_parse; - Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); - PlannerInfo **parent_roots = NULL; + List *tlist; + standard_qp_extra qp_extra; + RelOptInfo *planned_rel; + /* Inheritance is never used for insert. */ Assert(parse->commandType != CMD_INSERT); /* - * We generate a modified instance of the original Query for each target - * relation, plan that, and put all the plans into a list that will be - * controlled by a single ModifyTable node. All the instances share the - * same rangetable, but each instance must have its own set of subquery - * RTEs within the finished rangetable because (1) they are likely to get - * scribbled on during planning, and (2) it's not inconceivable that - * subqueries could get planned differently in different cases. We need - * not create duplicate copies of other RTE kinds, in particular not the - * target relations, because they don't have either of those issues. Not - * having to duplicate the target relations is important because doing so - * (1) would result in a rangetable of length O(N^2) for N targets, with - * at least O(N^3) work expended here; and (2) would greatly complicate - * management of the rowMarks list. + * Let query_planner generate the access paths for the query for each + * target child relation. * - * To begin with, generate a bitmapset of the relids of the subquery RTEs. + * First, save the unexpanded version of the query's targetlist. + * create_inherited_target_child_root will use it as base when expanding + * it for a given child relation as the query's target relation instead + * of the parent. */ - subqueryRTindexes = NULL; - rti = 1; - foreach(lc, parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - if (rte->rtekind == RTE_SUBQUERY) - subqueryRTindexes = bms_add_member(subqueryRTindexes, rti); - rti++; - } + root->unexpanded_tlist = list_copy(root->parse->targetList); + tlist = preprocess_targetlist(root); + root->processed_tlist = tlist; + qp_extra.tlist = tlist; + qp_extra.activeWindows = NIL; + qp_extra.groupClause = NIL; + planned_rel = query_planner(root, tlist, standard_qp_callback, &qp_extra); /* - * Next, we want to identify which AppendRelInfo items contain references - * to any of the aforesaid subquery RTEs. These items will need to be - * copied and modified to adjust their subquery references; whereas the - * other ones need not be touched. It's worth being tense over this - * because we can usually avoid processing most of the AppendRelInfo - * items, thereby saving O(N^2) space and time when the target is a large - * inheritance tree. We can identify AppendRelInfo items by their - * child_relid, since that should be unique within the list. + * If it turned out during query planning that all the children are dummy + * (pruned or excluded by constraints), no need to do the steps below. */ - modifiableARIindexes = NULL; - if (subqueryRTindexes != NULL) + if (IS_DUMMY_REL(planned_rel)) { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - - if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) || - bms_is_member(appinfo->child_relid, subqueryRTindexes) || - bms_overlap(pull_varnos((Node *) appinfo->translated_vars), - subqueryRTindexes)) - modifiableARIindexes = bms_add_member(modifiableARIindexes, - appinfo->child_relid); - } + /* + * Result path, even if dummy, must go into outer query's FINAL + * upperrel. + */ + final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL); + final_rel->reltarget = create_pathtarget(root, tlist); + mark_dummy_rel(final_rel); + return; } + Assert(planned_rel->relid == top_parentRTindex); + Assert(planned_rel->reloptkind == RELOPT_BASEREL); + /* * If the parent RTE is a partitioned table, we should use that as the * nominal target relation, because the RTEs added for partitioned tables @@ -1277,7 +1281,7 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); + parent_rte = planner_rt_fetch(top_parentRTindex, root); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1285,66 +1289,34 @@ inheritance_planner(PlannerInfo *root) } /* - * The PlannerInfo for each child is obtained by translating the relevant - * members of the PlannerInfo for its immediate parent, which we find - * using the parent_relid in its AppendRelInfo. We save the PlannerInfo - * for each parent in an array indexed by relid for fast retrieval. Since - * the maximum number of parents is limited by the number of RTEs in the - * query, we use that number to allocate the array. An extra entry is - * needed since relids start from 1. + * Get on with finalizing the path for each child target relation by + * adjust their Paths to get desired PathTarget. */ - parent_roots = (PlannerInfo **) palloc0((list_length(parse->rtable) + 1) * - sizeof(PlannerInfo *)); - parent_roots[top_parentRTindex] = root; - - /* - * And now we can get on with generating a plan for each child table. - */ - foreach(lc, root->append_rel_list) + forboth(lc1, root->inh_target_child_rels, + lc2, root->inh_target_child_path_rels) { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - PlannerInfo *subroot; + Index childRTindex = lfirst_int(lc1); + RelOptInfo *childbaserel = find_base_rel(root, childRTindex); + RelOptInfo *childjoinrel = lfirst(lc2); + PlannerInfo *subroot = root->inh_target_child_roots[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; RangeTblEntry *child_rte; RelOptInfo *sub_final_rel; + ListCell *lc; Path *subpath; + List *tlist = subroot->processed_tlist; + PathTarget *scanjoin_target; + bool tlist_same_exprs; - /* append_rel_list contains all append rels; ignore others */ - if (!bms_is_member(appinfo->parent_relid, parent_relids)) - continue; - - /* - * expand_inherited_rtentry() always processes a parent before any of - * that parent's children, so the parent_root for this relation should - * already be available. - */ - parent_root = parent_roots[appinfo->parent_relid]; - Assert(parent_root != NULL); - parent_parse = parent_root->parse; - - /* - * We need a working copy of the PlannerInfo so that we can control - * propagation of information back to the main copy. - */ - subroot = makeNode(PlannerInfo); - memcpy(subroot, parent_root, sizeof(PlannerInfo)); - - /* - * Generate modified query with this rel as target. We first apply - * adjust_appendrel_attrs, which copies the Query and changes - * references to the parent RTE to refer to the current child RTE, - * then fool around with subquery RTEs. - */ - subroot->parse = (Query *) - adjust_appendrel_attrs(parent_root, - (Node *) parent_parse, - 1, &appinfo); + Assert(subroot != NULL); + Assert(subroot->parse->resultRelation == childRTindex); /* * If there are securityQuals attached to the parent, move them to the * child rel (they've already been transformed properly for that). */ - parent_rte = rt_fetch(appinfo->parent_relid, subroot->parse->rtable); - child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable); + parent_rte = planner_rt_fetch(appinfo->parent_relid, subroot); + child_rte = planner_rt_fetch(appinfo->child_relid, subroot); child_rte->securityQuals = parent_rte->securityQuals; parent_rte->securityQuals = NIL; @@ -1356,24 +1328,12 @@ inheritance_planner(PlannerInfo *root) (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED; /* - * If this child is further partitioned, remember it as a parent. - * Since a partitioned table does not have any data, we don't need to - * create a plan for it, and we can stop processing it here. We do, - * however, need to remember its modified PlannerInfo for use when - * processing its children, since we'll update their varnos based on - * the delta from immediate parent to child, not from top to child. - * - * Note: a very non-obvious point is that we have not yet added - * duplicate subquery RTEs to the subroot's rtable. We mustn't, - * because then its children would have two sets of duplicates, - * confusing matters. + * Ignore a partitioned child. Instead, the paths of its children will + * be added to subpaths. */ - if (child_rte->inh) + if (childjoinrel->part_scheme) { - Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE); - parent_relids = bms_add_member(parent_relids, appinfo->child_relid); - parent_roots[appinfo->child_relid] = subroot; - + Assert(child_rte->inh); continue; } @@ -1401,161 +1361,57 @@ inheritance_planner(PlannerInfo *root) if (nominalRelation < 0) nominalRelation = appinfo->child_relid; - /* - * The rowMarks list might contain references to subquery RTEs, so - * make a copy that we can apply ChangeVarNodes to. (Fortunately, the - * executor doesn't need to see the modified copies --- we can just - * pass it the original rowMarks list.) - */ - subroot->rowMarks = copyObject(parent_root->rowMarks); - - /* - * The append_rel_list likewise might contain references to subquery - * RTEs (if any subqueries were flattenable UNION ALLs). So prepare - * to apply ChangeVarNodes to that, too. As explained above, we only - * want to copy items that actually contain such references; the rest - * can just get linked into the subroot's append_rel_list. - * - * If we know there are no such references, we can just use the outer - * append_rel_list unmodified. - */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - subroot->append_rel_list = NIL; - foreach(lc2, parent_root->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, modifiableARIindexes)) - appinfo2 = copyObject(appinfo2); - - subroot->append_rel_list = lappend(subroot->append_rel_list, - appinfo2); - } - } - - /* - * Add placeholders to the child Query's rangetable list to fill the - * RT indexes already reserved for subqueries in previous children. - * These won't be referenced, so there's no need to make them very - * valid-looking. - */ - while (list_length(subroot->parse->rtable) < list_length(final_rtable)) - subroot->parse->rtable = lappend(subroot->parse->rtable, - makeNode(RangeTblEntry)); - - /* - * If this isn't the first child Query, generate duplicates of all - * subquery RTEs, and adjust Var numbering to reference the - * duplicates. To simplify the loop logic, we scan the original rtable - * not the copy just made by adjust_appendrel_attrs; that should be OK - * since subquery RTEs couldn't contain any references to the target - * rel. - */ - if (final_rtable != NIL && subqueryRTindexes != NULL) - { - ListCell *lr; - - rti = 1; - foreach(lr, parent_parse->rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lr); - - if (bms_is_member(rti, subqueryRTindexes)) - { - Index newrti; - - /* - * The RTE can't contain any references to its own RT - * index, except in its securityQuals, so we can save a - * few cycles by applying ChangeVarNodes to the rest of - * the rangetable before we append the RTE to it. - */ - newrti = list_length(subroot->parse->rtable) + 1; - ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); - ChangeVarNodes((Node *) subroot->rowMarks, rti, newrti, 0); - /* Skip processing unchanging parts of append_rel_list */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - foreach(lc2, subroot->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, - modifiableARIindexes)) - ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); - } - } - rte = copyObject(rte); - ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); - subroot->parse->rtable = lappend(subroot->parse->rtable, - rte); - } - rti++; - } - } - - /* There shouldn't be any OJ info to translate, as yet */ - Assert(subroot->join_info_list == NIL); - /* and we haven't created PlaceHolderInfos, either */ - Assert(subroot->placeholder_list == NIL); - - /* Generate Path(s) for accessing this result relation */ - grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ ); + /* Apply the top-level targetlist to childjoinrel's path. */ + scanjoin_target = create_pathtarget(subroot, tlist); + tlist_same_exprs = equal(scanjoin_target->exprs, + childjoinrel->reltarget->exprs); + apply_scanjoin_target_to_paths_guts(subroot, childjoinrel, + scanjoin_target, + tlist_same_exprs); /* + * Transfer paths from childjoinrel's pathlist to sub_final_rel. * Select cheapest path in case there's more than one. We always run * modification queries to conclusion, so we care only for the * cheapest-total path. */ sub_final_rel = fetch_upper_rel(subroot, UPPERREL_FINAL, NULL); + foreach(lc, childjoinrel->pathlist) + add_path(sub_final_rel, (Path *) lfirst(lc)); set_cheapest(sub_final_rel); subpath = sub_final_rel->cheapest_total_path; /* - * If this child rel was excluded by constraint exclusion, exclude it - * from the result plan. + * If the childbaserel belongs to a single FDW, so does the + * corresponding sub_final_rel. */ - if (IS_DUMMY_PATH(subpath)) - continue; + sub_final_rel->serverid = childbaserel->serverid; + sub_final_rel->userid = childbaserel->userid; + sub_final_rel->useridiscurrent = childbaserel->useridiscurrent; + sub_final_rel->fdwroutine = childbaserel->fdwroutine; /* - * If this is the first non-excluded child, its post-planning rtable - * becomes the initial contents of final_rtable; otherwise, append - * just its modified subquery RTEs to final_rtable. + * If there is an FDW that's responsible for all baserels of the query, + * let it consider adding ForeignPaths. */ - if (final_rtable == NIL) - final_rtable = subroot->parse->rtable; - else - final_rtable = list_concat(final_rtable, - list_copy_tail(subroot->parse->rtable, - list_length(final_rtable))); + if (sub_final_rel->fdwroutine && + sub_final_rel->fdwroutine->GetForeignUpperPaths) + sub_final_rel->fdwroutine->GetForeignUpperPaths(subroot, + UPPERREL_FINAL, + childjoinrel, + sub_final_rel, + NULL); + + /* Let extensions possibly add some more paths to sub_final_rel. */ + if (create_upper_paths_hook) + (*create_upper_paths_hook) (subroot, UPPERREL_FINAL, childjoinrel, + sub_final_rel, NULL); /* - * We need to collect all the RelOptInfos from all child plans into - * the main PlannerInfo, since setrefs.c will need them. We use the - * last child's simple_rel_array (previous ones are too short), so we - * have to propagate forward the RelOptInfos that were already built - * in previous children. + * child rel cannot be empty, or inheritance_make_rel_from_joinlist + * wouldn't have put it in the list to begin with. */ - Assert(subroot->simple_rel_array_size >= save_rel_array_size); - for (rti = 1; rti < save_rel_array_size; rti++) - { - RelOptInfo *brel = save_rel_array[rti]; - - if (brel) - subroot->simple_rel_array[rti] = brel; - } - save_rel_array_size = subroot->simple_rel_array_size; - save_rel_array = subroot->simple_rel_array; - save_append_rel_array = subroot->append_rel_array; - - /* Make sure any initplans from this rel get into the outer list */ - root->init_plans = subroot->init_plans; + Assert(!IS_DUMMY_PATH(subpath)); /* Build list of sub-paths */ subpaths = lappend(subpaths, subpath); @@ -1587,36 +1443,6 @@ inheritance_planner(PlannerInfo *root) */ /* - * If we managed to exclude every child rel, return a dummy plan; it - * doesn't even need a ModifyTable node. - */ - if (subpaths == NIL) - { - set_dummy_rel_pathlist(final_rel); - return; - } - - /* - * Put back the final adjusted rtable into the master copy of the Query. - * (We mustn't do this if we found no non-excluded children.) - */ - parse->rtable = final_rtable; - root->simple_rel_array_size = save_rel_array_size; - root->simple_rel_array = save_rel_array; - root->append_rel_array = save_append_rel_array; - - /* Must reconstruct master's simple_rte_array, too */ - root->simple_rte_array = (RangeTblEntry **) - palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *)); - rti = 1; - foreach(lc, final_rtable) - { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); - - root->simple_rte_array[rti++] = rte; - } - - /* * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will * have dealt with fetching non-locked marked rows, else we need to have * ModifyTable do that. @@ -1651,11 +1477,6 @@ inheritance_planner(PlannerInfo *root) * This function adds all required top-level processing to the scan/join * Path(s) produced by query_planner. * - * If inheritance_update is true, we're being called from inheritance_planner - * and should not include a ModifyTable step in the resulting Path(s). - * (inheritance_planner will create a single ModifyTable node covering all the - * target tables.) - * * tuple_fraction is the fraction of tuples we expect will be retrieved. * tuple_fraction is interpreted as follows: * 0: expect all tuples to be retrieved (normal case) @@ -1673,8 +1494,7 @@ inheritance_planner(PlannerInfo *root) *-------------------- */ static void -grouping_planner(PlannerInfo *root, bool inheritance_update, - double tuple_fraction) +grouping_planner(PlannerInfo *root, double tuple_fraction) { Query *parse = root->parse; List *tlist; @@ -2172,11 +1992,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, offset_est, count_est); } - /* - * If this is an INSERT/UPDATE/DELETE, and we're not being called from - * inheritance_planner, add the ModifyTable node. - */ - if (parse->commandType != CMD_SELECT && !inheritance_update) + /* If this is an INSERT/UPDATE/DELETE, add the ModifyTable node. */ + if (parse->commandType != CMD_SELECT) { Index rootRelation; List *withCheckOptionLists; @@ -6914,7 +6731,6 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, bool scanjoin_target_parallel_safe, bool tlist_same_exprs) { - ListCell *lc; PathTarget *scanjoin_target; bool is_dummy_rel = IS_DUMMY_REL(rel); @@ -6985,53 +6801,8 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, /* Extract SRF-free scan/join target. */ scanjoin_target = linitial_node(PathTarget, scanjoin_targets); - - /* - * Adjust each input path. If the tlist exprs are the same, we can just - * inject the sortgroupref information into the existing pathtarget. - * Otherwise, replace each path with a projection path that generates the - * SRF-free scan/join target. This can't change the ordering of paths - * within rel->pathlist, so we just modify the list in place. - */ - foreach(lc, rel->pathlist) - { - Path *subpath = (Path *) lfirst(lc); - Path *newpath; - - Assert(subpath->param_info == NULL); - - if (tlist_same_exprs) - subpath->pathtarget->sortgrouprefs = - scanjoin_target->sortgrouprefs; - else - { - newpath = (Path *) create_projection_path(root, rel, subpath, - scanjoin_target); - lfirst(lc) = newpath; - } - } - - /* Same for partial paths. */ - foreach(lc, rel->partial_pathlist) - { - Path *subpath = (Path *) lfirst(lc); - Path *newpath; - - /* Shouldn't have any parameterized paths anymore */ - Assert(subpath->param_info == NULL); - - if (tlist_same_exprs) - subpath->pathtarget->sortgrouprefs = - scanjoin_target->sortgrouprefs; - else - { - newpath = (Path *) create_projection_path(root, - rel, - subpath, - scanjoin_target); - lfirst(lc) = newpath; - } - } + apply_scanjoin_target_to_paths_guts(root, rel, scanjoin_target, + tlist_same_exprs); /* Now fix things up if scan/join target contains SRFs */ if (root->parse->hasTargetSRFs) @@ -7059,6 +6830,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -7112,6 +6887,67 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, } /* + * apply_scanjoin_target_to_paths_guts + * Portion of apply_scanjoin_target_to_paths that's useful in some places + * where full powers of the former are unnecessary + */ +static void +apply_scanjoin_target_to_paths_guts(PlannerInfo *root, + RelOptInfo *rel, + PathTarget *scanjoin_target, + bool tlist_same_exprs) +{ + ListCell *lc; + + /* + * Adjust each input path. If the tlist exprs are the same, we can just + * inject the sortgroupref information into the existing pathtarget. + * Otherwise, replace each path with a projection path that generates the + * SRF-free scan/join target. This can't change the ordering of paths + * within rel->pathlist, so we just modify the list in place. + */ + foreach(lc, rel->pathlist) + { + Path *subpath = (Path *) lfirst(lc); + Path *newpath; + + Assert(subpath->param_info == NULL); + + if (tlist_same_exprs) + subpath->pathtarget->sortgrouprefs = + scanjoin_target->sortgrouprefs; + else + { + newpath = (Path *) create_projection_path(root, rel, subpath, + scanjoin_target); + lfirst(lc) = newpath; + } + } + + /* Same for partial paths. */ + foreach(lc, rel->partial_pathlist) + { + Path *subpath = (Path *) lfirst(lc); + Path *newpath; + + /* Shouldn't have any parameterized paths anymore */ + Assert(subpath->param_info == NULL); + + if (tlist_same_exprs) + subpath->pathtarget->sortgrouprefs = + scanjoin_target->sortgrouprefs; + else + { + newpath = (Path *) create_projection_path(root, + rel, + subpath, + scanjoin_target); + lfirst(lc) = newpath; + } + } +} + +/* * create_partitionwise_grouping_paths * * If the partition keys of input relation are part of the GROUP BY clause, all diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index ca6622ece9..8914c464d3 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -18,6 +18,8 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" #include "parser/parsetree.h" #include "utils/lsyscache.h" #include "utils/rel.h" @@ -394,8 +396,34 @@ adjust_appendrel_attrs_mutator(Node *node, context->appinfos); return (Node *) phv; } + + if (IsA(node, SpecialJoinInfo)) + { + SpecialJoinInfo *oldinfo = (SpecialJoinInfo *) node; + SpecialJoinInfo *newinfo = makeNode(SpecialJoinInfo); + + memcpy(newinfo, oldinfo, sizeof(SpecialJoinInfo)); + newinfo->min_lefthand = adjust_child_relids(oldinfo->min_lefthand, + context->nappinfos, + context->appinfos); + newinfo->min_righthand = adjust_child_relids(oldinfo->min_righthand, + context->nappinfos, + context->appinfos); + newinfo->syn_lefthand = adjust_child_relids(oldinfo->syn_lefthand, + context->nappinfos, + context->appinfos); + newinfo->syn_righthand = adjust_child_relids(oldinfo->syn_righthand, + context->nappinfos, + context->appinfos); + newinfo->semi_rhs_exprs = + (List *) expression_tree_mutator((Node *) + oldinfo->semi_rhs_exprs, + adjust_appendrel_attrs_mutator, + (void *) context); + return (Node *) newinfo; + } + /* Shouldn't need to handle planner auxiliary nodes here */ - Assert(!IsA(node, SpecialJoinInfo)); Assert(!IsA(node, AppendRelInfo)); Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, MinMaxAggInfo)); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index eaf788e578..1d16a9f2ff 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -21,11 +21,16 @@ #include "miscadmin.h" #include "optimizer/appendinfo.h" #include "optimizer/inherit.h" +#include "optimizer/optimizer.h" +#include "optimizer/paths.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "utils/rel.h" +static PlannerInfo *create_inherited_target_child_root(PlannerInfo *root, + AppendRelInfo *appinfo); static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti); static void expand_partitioned_rtentry(PlannerInfo *root, @@ -492,3 +497,125 @@ translate_col_privs(const Bitmapset *parent_privs, return child_privs; } + +/* + * add_inherited_target_child_roots + * For each child of the query's result relation, this translates the + * original query to match the child and creates a PlannerInfo containing + * the translated query + * + * Child PlannerInfo reuses most of the parent PlannerInfo's fields unchanged, + * except unexpanded_tlist, processed_tlist, and all_baserels, all of which + * are based on the child relation. + */ +void +add_inherited_target_child_roots(PlannerInfo *root) +{ + Index resultRelation = root->parse->resultRelation; + ListCell *lc; + + Assert(root->inh_target_child_roots != NULL); + + foreach(lc, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc); + RangeTblEntry *childRTE; + PlannerInfo *subroot; + + if (appinfo->parent_relid != resultRelation) + continue; + + /* + * Create a PlannerInfo for processing this child target relation + * with. + */ + subroot = create_inherited_target_child_root(root, appinfo); + root->inh_target_child_roots[appinfo->child_relid] = subroot; + + /* + * If the child is a partitioned table, recurse to do this for its + * partitions. + */ + childRTE = root->simple_rte_array[appinfo->child_relid]; + if (childRTE->inh) + add_inherited_target_child_roots(subroot); + } +} + +/* + * create_inherited_target_child_root + * Workhorse of add_inherited_target_child_roots + */ +static PlannerInfo * +create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) +{ + PlannerInfo *subroot; + List *tlist, + *tlist_vars; + + + Assert(root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE); + + /* + * Translate the original query to replace Vars of the parent table + * by the corresponding Vars of the child table and to make child the main + * target relation of the query. + */ + subroot = makeNode(PlannerInfo); + memcpy(subroot, root, sizeof(PlannerInfo)); + + /* + * Restore the original, unexpanded targetlist, that is, the one before + * preprocess_targetlist was run on the original query. We'll run + * preprocess_targetlist after translating the query and the targetlist, + * so that it is expanded according to child's tuple descriptor. + */ + root->parse->targetList = root->unexpanded_tlist; + subroot->parse = (Query *) adjust_appendrel_attrs(root, + (Node *) root->parse, + 1, &appinfo); + + /* + * Translate ECs. This copies the list of ECs, translating the + * relevant members of individual ECs to replace the references to + * the parent RTE by child RTE, including the EC members. + */ + subroot->eq_classes = + translate_eq_classes_for_child(root, root->eq_classes, + appinfo); + + /* + * Save the just translated targetlist as unexpanded_tlist in the child's + * subroot, so that this child's own children can use it. Must use copy + * because subroot->parse->targetList will be modified soon. + */ + subroot->unexpanded_tlist = list_copy(subroot->parse->targetList); + + /* + * Apply planner's expansion of targetlist, such as adding various junk + * column, filling placeholder entries for dropped columns, etc., using + * the child's TupleDesc. + */ + tlist = preprocess_targetlist(subroot); + subroot->processed_tlist = tlist; + + /* + * Add any newly added Vars to the child RelOptInfo. They'll be needed + * at the top level, so we pass {0} for where_needed. + */ + tlist_vars = pull_var_clause((Node *) tlist, + PVC_RECURSE_AGGREGATES | + PVC_RECURSE_WINDOWFUNCS | + PVC_INCLUDE_PLACEHOLDERS); + add_vars_to_targetlist(root, tlist_vars, bms_make_singleton(0), false); + + /* + * Adjust all_baserels to replace the original target relation with the + * child target relation. + */ + subroot->all_baserels = adjust_child_relids(subroot->all_baserels, + 1, &appinfo); + + return subroot; +} diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index a008ae07da..2dc3059f16 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -217,6 +217,17 @@ struct PlannerInfo struct AppendRelInfo **append_rel_array; /* + * inh_target_child_roots is same length as above arrays, indexed by + * rangetable index (entry 0 is wasted like simple_rel_array). Only + * elements corresponding to inheritance child target relations are + * non-NULL. Content of each PlannerInfo is mostly same as the parent + * PlannerInfo, except for few fields such as the parse tree which is + * a translated copy of the parent's parse tree, EC list which contain + * child member expressions, etc. + */ + struct PlannerInfo **inh_target_child_roots; + + /* * all_baserels is a Relids set of all base relids (but not "other" * relids) in the query; that is, the Relids identifier of the final join * we need to form. This is computed in make_one_rel, just before we @@ -310,6 +321,12 @@ struct PlannerInfo */ List *processed_tlist; + /* + * This stores the original version of the query's targetlist that's not + * modified by the planner. Useful for target inheritance planning. + */ + List *unexpanded_tlist; + /* Fields filled during create_plan() for use in setrefs.c */ AttrNumber *grouping_map; /* for GroupingFunc fixup */ List *minmax_aggs; /* List of MinMaxAggInfos */ @@ -348,9 +365,22 @@ struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* List of RT indexes of child target relations. */ + List *inh_target_child_rels; + + /* + * RelOptInfos corresponding to each child target rel. For leaf children, + * it's the RelOptInfo representing the output of make_rel_from_joinlist() + * called with the parent rel in the original join tree replaced by a + * given leaf child. For non-leaf children, it's the baserel RelOptInfo + * itself, left as a placeholder. + */ + List *inh_target_child_path_rels; }; + /* * In places where it's known that simple_rte_array[] must have been prepared * already, we just index into it to fetch RTEs. In code that might be diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h index d2418f15cf..4f06cdd322 100644 --- a/src/include/optimizer/inherit.h +++ b/src/include/optimizer/inherit.h @@ -18,5 +18,6 @@ extern void expand_inherited_tables(PlannerInfo *root); +extern void add_inherited_target_child_roots(PlannerInfo *root); #endif /* INHERIT_H */ diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 040335a7c5..e8caaaf643 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -154,6 +154,8 @@ extern void add_child_rel_equivalences(PlannerInfo *root, AppendRelInfo *appinfo, RelOptInfo *parent_rel, RelOptInfo *child_rel); +extern List *translate_eq_classes_for_child(PlannerInfo *root, + List *eq_classes, AppendRelInfo *appinfo); extern List *generate_implied_equalities_for_column(PlannerInfo *root, RelOptInfo *rel, ec_matches_callback_type callback, diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out index bbdc373782..fd85b5ead3 100644 --- a/src/test/regress/expected/partition_join.out +++ b/src/test/regress/expected/partition_join.out @@ -1780,7 +1780,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_1 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_1 -> Nested Loop Semi Join @@ -1788,7 +1788,7 @@ WHERE EXISTS ( Filter: (c IS NULL) -> Nested Loop -> Seq Scan on int4_tbl - -> Subquery Scan on ss_2 + -> Subquery Scan on ss -> Limit -> Seq Scan on int8_tbl int8_tbl_2 (28 rows) -- 2.11.0
0002-Get-rid-of-some-useless-code.patch (text/plain, 8.5K) download From c83f4ec48e035ce2a9fcb2c54a251ecb900f71e0 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 8 Feb 2019 16:21:59 +0900 Subject: [PATCH 2/5] Get rid of some useless code * plancat.c no longer needs to load partition constraints, because even update/delete now use partprune.c's facilities * relation_excluded_by_constraints no longer needs to worry about fielding update/delete on partitioned tablesm, which in turn means InheritanceKind is no longer necessary --- src/backend/nodes/outfuncs.c | 1 - src/backend/optimizer/plan/createplan.c | 10 ----- src/backend/optimizer/plan/planner.c | 8 ---- src/backend/optimizer/prep/prepjointree.c | 1 - src/backend/optimizer/util/plancat.c | 73 ++++--------------------------- src/include/nodes/pathnodes.h | 15 ------- 6 files changed, 9 insertions(+), 99 deletions(-) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index c5c08efac1..475dc68d8a 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2209,7 +2209,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); WRITE_UINT_FIELD(qual_security_level); - WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind); WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasHavingQual); diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 236f506cfb..7383eb9dfe 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -2054,12 +2054,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path) /* * During setrefs.c, we'll need the grouping_map to fix up the cols lists * in GroupingFunc nodes. Save it for setrefs.c to use. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be grouping in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->grouping_map == NULL); root->grouping_map = grouping_map; @@ -2216,12 +2211,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path) * with InitPlan output params. (We can't just do that locally in the * MinMaxAgg node, because path nodes above here may have Agg references * as well.) Save the mmaggregates list to tell setrefs.c to do that. - * - * This doesn't work if we're in an inheritance subtree (see notes in - * create_modifytable_plan). Fortunately we can't be because there would - * never be aggregates in an UPDATE/DELETE; but let's Assert that. */ - Assert(root->inhTargetKind == INHKIND_NONE); Assert(root->minmax_aggs == NIL); root->minmax_aggs = best_path->mmaggregates; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index cf64688e76..6a6403d7fe 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -639,7 +639,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->grouping_map = NULL; root->minmax_aggs = NIL; root->qual_security_level = 0; - root->inhTargetKind = INHKIND_NONE; root->hasRecursion = hasRecursion; if (hasRecursion) root->wt_param_id = assign_special_exec_param(root); @@ -1321,13 +1320,6 @@ inheritance_planner(PlannerInfo *root) parent_rte->securityQuals = NIL; /* - * Mark whether we're planning a query to a partitioned table or an - * inheritance parent. - */ - subroot->inhTargetKind = - (rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED; - - /* * Ignore a partitioned child. Instead, the paths of its children will * be added to subpaths. */ diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index aebe162713..8aef1f6535 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -894,7 +894,6 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, subroot->grouping_map = NULL; subroot->minmax_aggs = NIL; subroot->qual_security_level = 0; - subroot->inhTargetKind = INHKIND_NONE; subroot->hasRecursion = false; subroot->wt_param_id = -1; subroot->non_recursive_path = NULL; diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index d6dc83ca80..d8c1981b4d 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1266,36 +1266,6 @@ get_relation_constraints(PlannerInfo *root, } } - /* - * Append partition predicates, if any. - * - * For selects, partition pruning uses the parent table's partition bound - * descriptor, instead of constraint exclusion which is driven by the - * individual partition's partition constraint. - */ - if (enable_partition_pruning && root->parse->commandType != CMD_SELECT) - { - List *pcqual = RelationGetPartitionQual(relation); - - if (pcqual) - { - /* - * Run the partition quals through const-simplification similar to - * check constraints. We skip canonicalize_qual, though, because - * partition quals should be in canonical form already; also, - * since the qual is in implicit-AND format, we'd have to - * explicitly convert it to explicit-AND format and back again. - */ - pcqual = (List *) eval_const_expressions(root, (Node *) pcqual); - - /* Fix Vars to have the desired varno */ - if (varno != 1) - ChangeVarNodes((Node *) pcqual, 1, varno, 0); - - result = list_concat(result, pcqual); - } - } - table_close(relation, NoLock); return result; @@ -1419,40 +1389,15 @@ relation_excluded_by_constraints(PlannerInfo *root, /* * Skip further tests, depending on constraint_exclusion. */ - switch (constraint_exclusion) - { - case CONSTRAINT_EXCLUSION_OFF: - - /* - * Don't prune if feature turned off -- except if the relation is - * a partition. While partprune.c-style partition pruning is not - * yet in use for all cases (update/delete is not handled), it - * would be a UI horror to use different user-visible controls - * depending on such a volatile implementation detail. Therefore, - * for partitioned tables we use enable_partition_pruning to - * control this behavior. - */ - if (root->inhTargetKind == INHKIND_PARTITIONED) - break; - return false; - - case CONSTRAINT_EXCLUSION_PARTITION: - - /* - * When constraint_exclusion is set to 'partition' we only handle - * OTHER_MEMBER_RELs, or BASERELs in cases where the result target - * is an inheritance parent or a partitioned table. - */ - if ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) && - !(rel->reloptkind == RELOPT_BASEREL && - root->inhTargetKind != INHKIND_NONE && - rel->relid == root->parse->resultRelation)) - return false; - break; - - case CONSTRAINT_EXCLUSION_ON: - break; /* always try to exclude */ - } + if (constraint_exclusion == CONSTRAINT_EXCLUSION_OFF) + return false; + /* + * When constraint_exclusion is set to 'partition' we only handle + * OTHER_MEMBER_RELs. + */ + else if (constraint_exclusion == CONSTRAINT_EXCLUSION_PARTITION && + rel->reloptkind != RELOPT_OTHER_MEMBER_REL) + return false; /* * Check for self-contradictory restriction clauses. We dare not make diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 2dc3059f16..b219ff8806 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -82,18 +82,6 @@ typedef enum UpperRelationKind /* NB: UPPERREL_FINAL must be last enum entry; it's used to size arrays */ } UpperRelationKind; -/* - * This enum identifies which type of relation is being planned through the - * inheritance planner. INHKIND_NONE indicates the inheritance planner - * was not used. - */ -typedef enum InheritanceKind -{ - INHKIND_NONE, - INHKIND_INHERITED, - INHKIND_PARTITIONED -} InheritanceKind; - /*---------- * PlannerGlobal * Global information for planning/optimization @@ -342,9 +330,6 @@ struct PlannerInfo Index qual_security_level; /* minimum security_level for quals */ /* Note: qual_security_level is zero if there are no securityQuals */ - InheritanceKind inhTargetKind; /* indicates if the target relation is an - * inheritance child or partition or a - * partitioned table */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */ bool hasHavingQual; /* true if havingQual was non-null */ -- 2.11.0
0003-Lazy-creation-of-RTEs-for-inheritance-children.patch (text/plain, 99.0K) download From 039bd48dc21a6d1b59e4818dc5bcd4b791f9b209 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 26 Oct 2018 16:45:59 +0900 Subject: [PATCH 3/5] Lazy creation of RTEs for inheritance children Currently, expand_inherited_tables called from subquery_planner opens all inheritance child tables and adds them to the query's range table and PlannerInfo in the form of AppendRelInfo's initially and later RelOptInfo's. For partitioning, that's pretty wasteful, because it's possible to determine only the partitions that will need to be scanned using partition pruning, which doesn't require opening the partitions themselves. However, the point at which expand_inherited_tables is called currently is too early to perform partition pruning. This commit rearranges things within the planner so that expand_inherited_tables can be called from query_planner after the latter has finished setting up the information needed to perform partition pruning. However that means the PlannerInfo arrays that store RangeTblEntry's, RelOptInfo's, and AppendRelInfo's need to be expanded if inheritance expansion leads to adding more relations to planning. There are also consequences around targetlist expansion. For example, preprocess_targetlist cannot add the junk columns entries needed for row mark handling until all of the inheritance children have been determined, because different children may need different types of junk columns to be added. So, the approach of late inheritance expansion required some interface tweaks for preprocess_targetlist(). For partitioning, although we don't create a RangeTblEntry and RelOptInfo for pruned partitions at make_one_rel time, partitionwise join code relies on the fact that even though partitions may have been pruned, they'd still own a RelOptInfo to handle the outer join case where the pruned partition appears on the nullable side of join. Partitionwise join code deals with that by allocating dummy RelOptInfos for pruned partitions that are based mostly on their parent's properties. There are some regression test diffs: 1. Caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child, as seen in the partition_aggregate.out test output. 2. Those in postgres_fdw.out are caused by the fact that junk columns required for row marking are added to reltarget->exprs later than user columns, because the row marking junk columns aren't added until the inheritance is expanded which as of this commit is later than it used to be as noted above. --- contrib/postgres_fdw/expected/postgres_fdw.out | 32 +- src/backend/nodes/outfuncs.c | 1 + src/backend/optimizer/path/allpaths.c | 268 +------ src/backend/optimizer/path/joinrels.c | 71 +- src/backend/optimizer/plan/initsplan.c | 49 -- src/backend/optimizer/plan/planmain.c | 10 + src/backend/optimizer/plan/planner.c | 199 +++-- src/backend/optimizer/plan/setrefs.c | 6 + src/backend/optimizer/prep/preptlist.c | 15 +- src/backend/optimizer/prep/prepunion.c | 3 + src/backend/optimizer/util/appendinfo.c | 45 +- src/backend/optimizer/util/inherit.c | 841 ++++++++++++++++------ src/backend/optimizer/util/plancat.c | 47 +- src/backend/optimizer/util/relnode.c | 107 +-- src/backend/partitioning/partprune.c | 44 +- src/include/nodes/pathnodes.h | 19 +- src/include/optimizer/appendinfo.h | 7 +- src/include/optimizer/pathnode.h | 1 + src/include/optimizer/plancat.h | 4 +- src/include/optimizer/prep.h | 3 +- src/test/regress/expected/partition_aggregate.out | 4 +- 21 files changed, 1041 insertions(+), 735 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 42108bd3d4..4ba406af71 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7128,15 +7128,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7166,15 +7166,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7203,15 +7203,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid @@ -7221,15 +7221,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (39 rows) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 475dc68d8a..392bc8ff01 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2270,6 +2270,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_BOOL_FIELD(consider_partitionwise_join); WRITE_BITMAPSET_FIELD(top_parent_relids); WRITE_NODE_FIELD(partitioned_child_rels); + WRITE_UINT_FIELD(inh_root_parent); } static void diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 0a4729e646..749d173770 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -45,7 +45,6 @@ #include "optimizer/tlist.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -145,9 +144,6 @@ static void subquery_push_qual(Query *subquery, static void recurse_push_qual(Node *setOp, Query *topquery, RangeTblEntry *rte, Index rti, Node *qual); static void remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel); -static bool apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, - RelOptInfo *childrel, - RangeTblEntry *childRTE, AppendRelInfo *appinfo); /* @@ -390,6 +386,14 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, else if (rte->inh) { /* + * expand_inherited_tables may have proved that the relation is empty. + * For example, if it's a partitioned table with 0 partitions or all + * of its partitions are pruned. In that case nothing to do here. + */ + if (IS_DUMMY_REL(rel)) + return; + + /* * If it's a target relation, set the sizes of children instead. * Otherwise, we'll append the outputs of children, so process it as * an "append relation". @@ -986,29 +990,12 @@ set_inherited_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, int parentRTindex = rti; bool has_live_children = false; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); Assert(IS_SIMPLE_REL(rel)); - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - foreach(l, root->append_rel_list) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); @@ -1025,47 +1012,23 @@ set_inherited_target_rel_sizes(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); + Assert(childrel != NULL); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - /* - * 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 - * baserestrictinfo quals. + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals or quals that contradict its + * constraints. */ - if (!apply_child_basequals(root, rel, childrel, childRTE, appinfo)) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); + if (IS_DUMMY_REL(childrel)) continue; - } - - if (relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* - * This child need not be scanned, so we can omit it from the - * appendrel. - */ - set_dummy_rel_pathlist(childrel); - continue; - } /* * Add missing Vars to child's reltarget. @@ -1192,8 +1155,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -1201,32 +1162,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Assert(IS_SIMPLE_REL(rel)); /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, we will bubble up - * the indexes of partitioned relations that appear down in the tree, so - * that when we've created Paths for all the children, the root - * partitioned table's list will contain all such indexes. - */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - rel->partitioned_child_rels = list_make1_int(rti); - - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - - /* * If this is a partitioned baserel, set the consider_partitionwise_join * flag; currently, we only consider partitionwise joins with the baserel * if its targetlist doesn't contain a whole-row Var. @@ -1272,50 +1207,26 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); + Assert(childrel != NULL); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - /* - * 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 - * baserestrictinfo quals. + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals or quals that contradict its + * constraints. */ - if (!apply_child_basequals(root, rel, childrel, childRTE, appinfo)) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); + if (IS_DUMMY_REL(childrel)) continue; - } - - if (relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* - * This child need not be scanned, so we can omit it from the - * appendrel. - */ - set_dummy_rel_pathlist(childrel); - continue; - } /* - * CE failed, so finish copying/modifying targetlist and join quals. + * Copy/Modify targetlist. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -2849,6 +2760,7 @@ inheritance_make_rel_from_joinlist(PlannerInfo *root, List *joinlist) continue; childrel = find_base_rel(root, appinfo->child_relid); + Assert(childrel != NULL); /* Ignore excluded/pruned children. */ if (IS_DUMMY_REL(childrel)) @@ -3944,134 +3856,6 @@ generate_partitionwise_join_paths(PlannerInfo *root, RelOptInfo *rel) list_free(live_children); } -/* - * apply_child_basequals - * Populate childrel's quals based on rel's quals, translating them using - * appinfo. - * - * If any of the resulting clauses evaluate to false or NULL, we return false - * and don't apply any quals. Caller can mark the relation as a dummy rel in - * this case, since it needn't be scanned. - * - * If any resulting clauses evaluate to true, they're unnecessary and we don't - * apply then. - */ -static bool -apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, - RelOptInfo *childrel, RangeTblEntry *childRTE, - AppendRelInfo *appinfo) -{ - List *childquals; - Index cq_min_security; - ListCell *lc; - - /* - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities for - * const-simplification, and perhaps even pseudoconstant quals. Therefore, - * transform each RestrictInfo separately to see if it reduces to a - * constant or pseudoconstant. (We must process them separately to keep - * track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - return false; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. (Currently - * this can only happen in appendrels originating from UNION ALL; - * inheritance child tables don't have their own securityQuals, see - * expand_inherited_rtentry().) Pull any such securityQuals up into the - * baserestrictinfo for the child. This is similar to - * process_security_barrier_quals() for the parent rel, except that we - * can't make any general deductions from such quals, since they don't - * hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - return true; -} - /***************************************************************************** * DEBUG SUPPORT *****************************************************************************/ diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index dfbbfdac6d..6f77d2a0f4 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -21,6 +21,7 @@ #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" +#include "optimizer/tlist.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -52,6 +53,9 @@ static SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, Relids left_relids, Relids right_relids); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, + int partidx); /* @@ -1384,6 +1388,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (IS_SIMPLE_REL(rel1) && child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, cnt_parts); + if (IS_SIMPLE_REL(rel1) && child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, cnt_parts); + /* * If a child table has consider_partitionwise_join=false, it means * that it's a dummy relation for which we skipped setting up tlist @@ -1462,8 +1471,14 @@ update_child_rel_info(PlannerInfo *root, (Node *) rel->reltarget->exprs, 1, &appinfo); - /* Make child entries in the EquivalenceClass as well */ - if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) + /* + * Make child entries in the EquivalenceClass as well. If the childrel + * appears to be a dummy one (one built by build_dummy_partition_rel()), + * no need to make any new entries, because anything that'd need those + * can instead use the parent's (rel). + */ + if (childrel->relid != rel->relid && + (rel->has_eclass_joins || has_useful_pathkeys(root, rel))) add_child_rel_equivalences(root, appinfo, rel, childrel); childrel->has_eclass_joins = rel->has_eclass_joins; } @@ -1674,3 +1689,55 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +static RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, int partidx) +{ + RangeTblEntry *parentrte = root->simple_rte_array[parent->relid]; + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + AppendRelInfo *appinfo = make_append_rel_info(parent, parentrte, + parent->tupdesc, + parentrte->relid, + parent->reltype, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 2afc3f1dfe..5e8a8b1eee 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -419,7 +419,6 @@ void create_lateral_join_info(PlannerInfo *root) { bool found_laterals = false; - Relids prev_parents PG_USED_FOR_ASSERTS_ONLY = NULL; Index rti; ListCell *lc; @@ -618,54 +617,6 @@ create_lateral_join_info(PlannerInfo *root) bms_add_member(brel2->lateral_referencers, rti); } } - - /* - * Lastly, propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - * - * It's possible for child rels to have their own children, in which case - * the topmost parent's lateral info must be propagated all the way down. - * This code handles that case correctly so long as append_rel_list has - * entries for child relationships before grandchild relationships, which - * is an okay assumption right now, but we'll need to be careful to - * preserve it. The assertions below check for incorrect ordering. - */ - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); - RelOptInfo *parentrel = root->simple_rel_array[appinfo->parent_relid]; - RelOptInfo *childrel = root->simple_rel_array[appinfo->child_relid]; - - /* - * If we're processing a subquery of a query with inherited target rel - * (cf. inheritance_planner), append_rel_list may contain entries for - * tables that are not part of the current subquery and hence have no - * RelOptInfo. Ignore them. We can ignore dead rels, too. - */ - if (parentrel == NULL || !IS_SIMPLE_REL(parentrel)) - continue; - - /* Verify that children are processed before grandchildren */ -#ifdef USE_ASSERT_CHECKING - prev_parents = bms_add_member(prev_parents, appinfo->parent_relid); - Assert(!bms_is_member(appinfo->child_relid, prev_parents)); -#endif - - /* OK, propagate info down */ - Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - Assert(childrel->direct_lateral_relids == NULL); - childrel->direct_lateral_relids = parentrel->direct_lateral_relids; - Assert(childrel->lateral_relids == NULL); - childrel->lateral_relids = parentrel->lateral_relids; - Assert(childrel->lateral_referencers == NULL); - childrel->lateral_referencers = parentrel->lateral_referencers; - } } diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index c7f2323852..ae570e6abe 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -284,6 +284,16 @@ query_planner(PlannerInfo *root, List *tlist, } /* + * Expand RT entries that represent inherited or partitioned tables. + * This will perform partition pruning on partitioned tables in the + * original range table and also recursively on any child partitioned + * tables that were added by the expansion of the original parent(s). + * As new entries are added to the range table, various arrays in the + * PlannerInfo will be expanded accordingly. + */ + expand_inherited_tables(root); + + /* * Add child subroots needed during planning for individual child targets. */ if (parse->resultRelation && diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 6a6403d7fe..ce0ced792f 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -25,6 +25,7 @@ #include "access/table.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -131,6 +132,8 @@ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static void inheritance_planner(PlannerInfo *root); static void grouping_planner(PlannerInfo *root, double tuple_fraction); +static Path *create_single_target_modifytable_path(PlannerInfo *root, + RelOptInfo *final_rel, Path *subpath); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, int *tleref_to_colnum_map); @@ -719,27 +722,24 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } /* * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). + * pullup (so that all non-inherited RTEs are present). */ preprocess_rowmarks(root); /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain RTE_RELATION entries, so it's OK (and marginally more efficient) - * to do it after checking for joins and other special RTEs. We must do - * this after pulling up subqueries, else we'd fail to handle inherited - * tables in subqueries. - */ - expand_inherited_tables(root); - - /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -1247,7 +1247,9 @@ inheritance_planner(PlannerInfo *root) * of the parent. */ root->unexpanded_tlist = list_copy(root->parse->targetList); - tlist = preprocess_targetlist(root); + + /* We haven't expanded inheritance yet, so pass false. */ + tlist = preprocess_targetlist(root, false); root->processed_tlist = tlist; qp_extra.tlist = tlist; qp_extra.activeWindows = NIL; @@ -1257,16 +1259,47 @@ inheritance_planner(PlannerInfo *root) /* * If it turned out during query planning that all the children are dummy * (pruned or excluded by constraints), no need to do the steps below. + * For regular inheritance case, it's possible that we only need to modify + * the parent table. */ - if (IS_DUMMY_REL(planned_rel)) + parent_rte = planner_rt_fetch(top_parentRTindex, root); + if (!parent_rte->inh || IS_DUMMY_REL(planned_rel)) { + /* Top-level query targetlit determines final_rel's PathTarget. */ + PathTarget *scanjoin_target = create_pathtarget(root, tlist); + /* * Result path, even if dummy, must go into outer query's FINAL * upperrel. */ final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL); - final_rel->reltarget = create_pathtarget(root, tlist); - mark_dummy_rel(final_rel); + final_rel->reltarget = scanjoin_target; + + /* Check if we need projection to produce final PathTarget? */ + if (!IS_DUMMY_REL(planned_rel)) + { + bool tlist_exprs_same = equal(planned_rel->reltarget->exprs, + scanjoin_target->exprs); + + apply_scanjoin_target_to_paths_guts(root, planned_rel, + scanjoin_target, + tlist_exprs_same); + + /* Add ModifyTable on top of each path and add to final_rel. */ + foreach(lc1, planned_rel->pathlist) + { + Path *path = (Path *) lfirst(lc1); + + path = create_single_target_modifytable_path(root, final_rel, + path); + add_path(final_rel, path); + } + } + else + { + /* Empty Result path. */ + mark_dummy_rel(final_rel); + } return; } @@ -1280,7 +1313,6 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = planner_rt_fetch(top_parentRTindex, root); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1627,8 +1659,11 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) parse->groupClause = preprocess_groupclause(root, NIL); } - /* Preprocess targetlist */ - tlist = preprocess_targetlist(root); + /* + * Preprocess targetlist. Haven't expanded inheritance yet, so pass + * false. + */ + tlist = preprocess_targetlist(root, false); /* * We are now done hacking up the query's targetlist. Most of the @@ -1987,60 +2022,8 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) /* If this is an INSERT/UPDATE/DELETE, add the ModifyTable node. */ if (parse->commandType != CMD_SELECT) { - Index rootRelation; - List *withCheckOptionLists; - List *returningLists; - List *rowMarks; - - /* - * If target is a partition root table, we need to mark the - * ModifyTable node appropriately for that. - */ - if (rt_fetch(parse->resultRelation, parse->rtable)->relkind == - RELKIND_PARTITIONED_TABLE) - rootRelation = parse->resultRelation; - else - rootRelation = 0; - - /* - * Set up the WITH CHECK OPTION and RETURNING lists-of-lists, if - * needed. - */ - if (parse->withCheckOptions) - withCheckOptionLists = list_make1(parse->withCheckOptions); - else - withCheckOptionLists = NIL; - - if (parse->returningList) - returningLists = list_make1(parse->returningList); - else - returningLists = NIL; - - /* - * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node - * will have dealt with fetching non-locked marked rows, else we - * need to have ModifyTable do that. - */ - if (parse->rowMarks) - rowMarks = NIL; - else - rowMarks = root->rowMarks; - - path = (Path *) - create_modifytable_path(root, final_rel, - parse->commandType, - parse->canSetTag, - parse->resultRelation, - rootRelation, - false, - list_make1_int(parse->resultRelation), - list_make1(path), - list_make1(root), - withCheckOptionLists, - returningLists, - rowMarks, - parse->onConflict, - assign_special_exec_param(root)); + path = create_single_target_modifytable_path(root, final_rel, + path); } /* And shove it into final_rel */ @@ -2081,6 +2064,66 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) /* Note: currently, we leave it to callers to do set_cheapest() */ } +static Path * +create_single_target_modifytable_path(PlannerInfo *root, + RelOptInfo *final_rel, + Path *subpath) +{ + Query *parse = root->parse; + Index rootRelation; + List *withCheckOptionLists; + List *returningLists; + List *rowMarks; + + /* + * If target is a partition root table, we need to mark the ModifyTable + * node appropriately for that. + */ + if (rt_fetch(parse->resultRelation, parse->rtable)->relkind == + RELKIND_PARTITIONED_TABLE) + rootRelation = parse->resultRelation; + else + rootRelation = 0; + + /* + * Set up the WITH CHECK OPTION and RETURNING lists-of-lists, if needed. + */ + if (parse->withCheckOptions) + withCheckOptionLists = list_make1(parse->withCheckOptions); + else + withCheckOptionLists = NIL; + + if (parse->returningList) + returningLists = list_make1(parse->returningList); + else + returningLists = NIL; + + /* + * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node + * will have dealt with fetching non-locked marked rows, else we + * need to have ModifyTable do that. + */ + if (parse->rowMarks) + rowMarks = NIL; + else + rowMarks = root->rowMarks; + + return (Path *) create_modifytable_path(root, final_rel, + parse->commandType, + parse->canSetTag, + parse->resultRelation, + rootRelation, + false, + list_make1_int(parse->resultRelation), + list_make1(subpath), + list_make1(root), + withCheckOptionLists, + returningLists, + rowMarks, + parse->onConflict, + assign_special_exec_param(root)); +} + /* * Do preprocessing for groupingSets clause and related data. This handles the * preliminary steps of expanding the grouping sets, organizing them into lists @@ -2406,7 +2449,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2431,7 +2474,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } @@ -6988,6 +7031,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 0213a37670..154ccda432 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -326,6 +326,12 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) UPPERREL_FINAL, NULL))) add_rtes_to_flat_rtable(rel->subroot, true); } + /* + * A NULL rel also means an unplanned subquery rte, so apply + * flatten_unplanned_rtes. + */ + else + flatten_unplanned_rtes(glob, rte); } rti++; } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 5392d1a561..41a57d16b2 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -65,9 +65,13 @@ static List *expand_targetlist(List *tlist, int command_type, * * As a side effect, if there's an ON CONFLICT UPDATE clause, its targetlist * is also preprocessed (and updated in-place). + * + * inheritance_expanded specifies whether inheritance tables are expanded + * as of calling this function. Caller must specify it because that affects + * which row marking related junk columns get added to the targetlist. */ List * -preprocess_targetlist(PlannerInfo *root) +preprocess_targetlist(PlannerInfo *root, bool inheritance_expanded) { Query *parse = root->parse; int result_relation = parse->resultRelation; @@ -134,6 +138,15 @@ preprocess_targetlist(PlannerInfo *root) if (rc->rti != rc->prti) continue; + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which will change the parent row + * mark's allMarkTypes fields. + */ + if (rc->isParent && !inheritance_expanded) + continue; + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) { /* Need to fetch TID */ diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 55eeb5127c..7bed914f35 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -31,12 +31,15 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" #include "optimizer/cost.h" +#include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index 8914c464d3..16cd65da71 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -33,10 +33,10 @@ typedef struct AppendRelInfo **appinfos; } adjust_appendrel_attrs_context; -static void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); +static void make_inh_translation_list(TupleDesc old_tupdesc, + TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars); static Node *adjust_appendrel_attrs_mutator(Node *node, adjust_appendrel_attrs_context *context); static List *adjust_inherited_tlist(List *tlist, @@ -48,18 +48,20 @@ static List *adjust_inherited_tlist(List *tlist, * Build an AppendRelInfo for the parent-child pair */ AppendRelInfo * -make_append_rel_info(Relation parentrel, Relation childrel, - Index parentRTindex, Index childRTindex) +make_append_rel_info(RelOptInfo *parent, RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childtype, + Index childRTindex) { AppendRelInfo *appinfo = makeNode(AppendRelInfo); - appinfo->parent_relid = parentRTindex; + appinfo->parent_relid = parent->relid; appinfo->child_relid = childRTindex; - appinfo->parent_reltype = parentrel->rd_rel->reltype; - appinfo->child_reltype = childrel->rd_rel->reltype; - make_inh_translation_list(parentrel, childrel, childRTindex, - &appinfo->translated_vars); - appinfo->parent_reloid = RelationGetRelid(parentrel); + appinfo->parent_reltype = parent->reltype; + appinfo->child_reltype = childtype; + make_inh_translation_list(parent->tupdesc, childdesc, + parentrte->relid, childoid, + childRTindex, &appinfo->translated_vars); + appinfo->parent_reloid = parentrte->relid; return appinfo; } @@ -72,14 +74,11 @@ make_append_rel_info(Relation parentrel, Relation childrel, * For paranoia's sake, we match type/collation as well as attribute name. */ static void -make_inh_translation_list(Relation oldrelation, Relation newrelation, - Index newvarno, - List **translated_vars) +make_inh_translation_list(TupleDesc old_tupdesc, TupleDesc new_tupdesc, + Oid from_rel, Oid to_rel, + Index newvarno, List **translated_vars) { List *vars = NIL; - TupleDesc old_tupdesc = RelationGetDescr(oldrelation); - TupleDesc new_tupdesc = RelationGetDescr(newrelation); - Oid new_relid = RelationGetRelid(newrelation); int oldnatts = old_tupdesc->natts; int newnatts = new_tupdesc->natts; int old_attno; @@ -109,7 +108,7 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, * When we are generating the "translation list" for the parent table * of an inheritance set, no need to search for matches. */ - if (oldrelation == newrelation) + if (from_rel == to_rel) { vars = lappend(vars, makeVar(newvarno, (AttrNumber) (old_attno + 1), @@ -135,10 +134,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, { HeapTuple newtup; - newtup = SearchSysCacheAttName(new_relid, attname); + newtup = SearchSysCacheAttName(to_rel, attname); if (!newtup) elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1; ReleaseSysCache(newtup); @@ -148,10 +147,10 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation, /* Found it, check type and collation match */ if (atttypid != att->atttypid || atttypmod != att->atttypmod) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); if (attcollation != att->attcollation) elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation", - attname, RelationGetRelationName(newrelation)); + attname, get_rel_name(to_rel)); vars = lappend(vars, makeVar(newvarno, (AttrNumber) (new_attno + 1), diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 1d16a9f2ff..492757fd8c 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -18,35 +18,47 @@ #include "access/table.h" #include "catalog/partition.h" #include "catalog/pg_inherits.h" +#include "catalog/pg_type.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" #include "optimizer/inherit.h" #include "optimizer/optimizer.h" +#include "optimizer/pathnode.h" #include "optimizer/paths.h" +#include "optimizer/plancat.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" +#include "partitioning/partprune.h" #include "utils/rel.h" static PlannerInfo *create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo); -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); +static void expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti); +static void expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, + RangeTblEntry *rte, Index rti, RelOptInfo *rel); static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); + Index parentRTindex, RelOptInfo *parentrel); +static RelOptInfo *add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); - +static RelOptInfo *build_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); +static bool apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *childrel, + RangeTblEntry *childRTE, AppendRelInfo *appinfo); /* * expand_inherited_tables @@ -54,37 +66,145 @@ static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, * into an "append relation". At the conclusion of this process, * the "inh" flag is set in all and only those RTEs that are append * relation parents. + * + * Note that although we're calling the combined output of inheritance set + * relations an "append relation" here, the caller may not always want to + * combine the relations. For example, if the parent of the inheritance + * set is the query's target relation, each child relation is processed + * on its own as the query's target relation. */ void expand_inherited_tables(PlannerInfo *root) { - Index nrtes; + int orig_rtable_size; Index rti; - ListCell *rl; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; /* * expand_inherited_rtentry may add RTEs to parse->rtable. The function is * expected to recursively handle any RTEs that it creates with inh=true. * So just scan as far as the original end of the rtable list. */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) + for (rti = 1; rti < orig_rtable_size; rti++) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); + /* there may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + if (rte->inh) + expand_inherited_rtentry(root, brel, rte, rti); } } /* * expand_inherited_rtentry - * Check whether a rangetable entry represents an inheritance set. - * If so, add entries for all the child tables to the query's - * rangetable, and build AppendRelInfo nodes for all the child tables - * and add them to root->append_rel_list. If not, clear the entry's - * "inh" flag to prevent later code from looking for AppendRelInfos. + * This initializes RelOptInfos for inheritance child relations if the + * passed-in relation has any + * + * 'rel' is the parent relation, whose range table entry ('rte') has been + * marked to require adding children. Parent could either be a subquery (if + * we flattened UNION ALL query) or a table that's known to have (or once had) + * inheritance children. The latter consists of both regular inheritance + * parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as a child to be scanned as part of the + * inheritance set. The original RTE is considered to represent the whole + * inheritance set. + */ +static void +expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_inheritance_child_rel(root, rel, appinfo->child_relid); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rel); + else + expand_nonpartitioned_inherited_rtentry(root, rte, rti, rel); + } +} + +/* + * expand_nonpartitioned_inherited_rtentry + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. * * Note that the original RTE is considered to represent the whole * inheritance set. The first of the generated RTEs is an RTE for the same @@ -95,55 +215,35 @@ expand_inherited_tables(PlannerInfo *root) * regular inheritance, a parent RTE must always have at least two associated * AppendRelInfos: one corresponding to the parent table as a simple member of * inheritance set and one or more corresponding to the actual children. - * Since a partitioned table is not scanned, it might have only one associated - * AppendRelInfo. */ static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, + RangeTblEntry *rte, + Index rti, + RelOptInfo *rel) { Oid parentOID; PlanRowMark *oldrc; - Relation oldrelation; - LOCKMODE lockmode; + LOCKMODE lockmode = rte->rellockmode; List *inhOIDs; ListCell *l; + int num_children; + int num_children_added = 0; - /* Does RT entry allow inheritance? */ - if (!rte->inh) - return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } - /* Fast path for common case of childless table */ + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); parentOID = rte->relid; - if (!has_subclass(parentOID)) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * The rewriter should already have obtained an appropriate lock on each - * relation named in the query. However, for each child relation we add - * to the query, we must obtain an appropriate lock, because this will be - * the first use of those relations in the parse/rewrite/plan pipeline. - * Child rels should use the same lockmode as their parent. - */ - lockmode = rte->rellockmode; /* Scan for all members of inheritance set, acquire needed locks */ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); /* * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite above has_subclass() check, if table - * once had a child but no longer does. + * case. This could happen despite has_subclass() check performed by + * subquery_planner, if table once had a child but no longer does. */ - if (list_length(inhOIDs) < 2) + num_children = list_length(inhOIDs); + if (num_children < 2) { /* Clear flag before returning */ rte->inh = false; @@ -151,216 +251,221 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } /* - * If parent relation is selected FOR UPDATE/SHARE, we need to mark its - * PlanRowMark as isParent = true, and generate a new PlanRowMark for each - * child. + * If parent relation is selected FOR UPDATE/SHARE, preprocess_rowmarks + * should've set isParent = true. We'll generate a new PlanRowMark for + * each child. */ oldrc = get_plan_rowmark(root->rowMarks, rti); - if (oldrc) - oldrc->isParent = true; + Assert(oldrc == NULL || oldrc->isParent); /* - * Must open the parent relation to examine its tupdesc. We need not lock - * it; we assume the rewriter already did. + * Must expand PlannerInfo arrays by num_children before we can add + * children. */ - oldrelation = table_open(parentOID, NoLock); + Assert(num_children > 0); + expand_planner_arrays(root, num_children); - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) + foreach(l, inhOIDs) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); - - /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. - */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); - } - else - { - List *appinfos = NIL; + Oid childOID = lfirst_oid(l); + Relation newrelation; RangeTblEntry *childrte; Index childRTindex; + /* Already locked above. */ + newrelation = heap_open(childOID, NoLock); + /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. */ - foreach(l, inhOIDs) + if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = table_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - table_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - table_close(newrelation, NoLock); + table_close(newrelation, lockmode); + continue; } - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); + (void) add_inheritance_child_rel(root, rte, rti, rel, oldrc, + newrelation, &childrte, + &childRTindex); + Assert(childrte != NULL); + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + Assert(childRTindex > 0); + /* Close child relations, but keep locks */ + heap_close(newrelation, NoLock); + num_children_added++; } - table_close(oldrelation, NoLock); + /* + * If all children, including the parent (as child rel), were + * excluded, mark the parent rel as empty. If all the children were temp + * tables, pretend it's a non-inheritance situation; we don't need Append + * node in that case. The duplicate RTE we added for the parent table is + * harmless, so we don't bother to get rid of it; ditto for the useless + * PlanRowMark node. + */ + if (num_children_added == 0) + mark_dummy_rel(rel); + else if (num_children_added == 1) + rte->inh = false; + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } } /* * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) + Index parentRTindex, RelOptInfo *parentrel) { + LOCKMODE lockmode = parentrte->rellockmode; + PlanRowMark *rootrc = NULL; int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); + Bitmapset *partindexes; + Index rootParentRTindex = parentrel->inh_root_parent > 0 ? + parentrel->inh_root_parent : + parentRTindex; - check_stack_depth(); - - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will be + * bubbled up into root parent's list so that when we've created Paths for + * all the children, the root table's list will contain all such indexes. */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + parentrel->partitioned_child_rels = list_make1_int(parentRTindex); - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parentrel); + + /* Must expand PlannerInfo arrays before we can add children. */ + if (bms_num_members(partindexes) > 0) + expand_planner_arrays(root, bms_num_members(partindexes)); /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. */ - if (partdesc->nparts == 0) - { - parentrte->inh = false; - return; - } + parentrel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parentrel->nparts); - for (i = 0; i < partdesc->nparts; i++) + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) { - Oid childOID = partdesc->oids[i]; - Relation childrel; + Oid childOID = parentrel->part_oids[i]; + Relation newrelation; + RelOptInfo *childrel; + RangeTblEntry *childrte; + Index childRTindex; /* Open rel; we already have required locks */ - childrel = table_open(childOID, NoLock); + newrelation = table_open(childOID, NoLock); + Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. + * A partitioned child table with 0 children is a dummy rel, so don't + * bother creating planner objects for it. */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); + if (newrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(newrelation)->nparts == 0) + { + heap_close(newrelation, NoLock); + continue; + } - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); + childrel = add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, newrelation, + &childrte, &childRTindex); + Assert(childrel != NULL); + parentrel->part_rels[i] = childrel; - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + /* Close child relations, but keep locks */ + table_close(newrelation, NoLock); + + /* If the child is partitioned itself, expand it too. */ + if (childrel->part_scheme) + { + Assert(childrte->inh); expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); + childrel); + } + } - /* Close child relation, but keep locks */ - table_close(childrel, NoLock); + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); } } /* - * expand_single_inheritance_child - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) + * The return value is the RelOptInfo that's added. * * PlanRowMarks still carry the top-parent's RTI, and the top-parent's * allMarkTypes field still accumulates values from all descendents. * * "parentrte" and "parentRTindex" are immediate parent's RTE and * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. */ -static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +static RelOptInfo * +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, RelOptInfo *parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); Oid childOID = RelationGetRelid(childrel); RangeTblEntry *childrte; Index childRTindex; AppendRelInfo *appinfo; + RelOptInfo *childrelopt; /* * Build an RTE for the child, and attach to query's rangetable list. We * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh = false. Also, set requiredPerms to zero since - * all required permissions checks are done on the original RTE. Likewise, - * set the child's securityQuals to empty, because we only want to apply - * the parent's RLS conditions regardless of what RLS properties + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties * individual children may have. (This is an intentional choice to make * inherited RLS work like regular permissions checks.) The parent * securityQuals will be propagated to children along with other base @@ -370,49 +475,42 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childrte_p = childrte; childrte->relid = childOID; childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) - childrte->inh = true; - else - childrte->inh = false; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); childrte->requiredPerms = 0; childrte->securityQuals = NIL; parse->rtable = lappend(parse->rtable, childrte); childRTindex = list_length(parse->rtable); *childRTindex_p = childRTindex; - /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. - */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) - { - appinfo = make_append_rel_info(parentrel, childrel, - parentRTindex, childRTindex); - *appinfos = lappend(*appinfos, appinfo); + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, parentrte, + RelationGetDescr(childrel), + RelationGetRelid(childrel), + RelationGetForm(childrel)->reltype, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); } /* @@ -444,6 +542,147 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, root->rowMarks = lappend(root->rowMarks, childrc); } + + /* + * Add the RelOptInfo. Even though we may not really scan this relation + * for reasons such as contradictory quals, we still need to create one, + * because for every RTE in the query's range table, there must be an + * accompanying RelOptInfo. + */ + + /* First, store the RTE and appinfos into planner arrays. */ + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + childrelopt = build_inheritance_child_rel(root, parentrel, childRTindex); + Assert(childrelopt != NULL); + + return childrelopt; +} + +/* + * build_inheritance_child_rel + * Build a RelOptInfo for child relation of an inheritance set + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields based on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turn out to be self-contradictory, + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + * + * It's possible for child rels to have their own children, in which case + * the topmost parent's lateral info must be propagated all the way down. + * That's ensured by having childrel be expanded via this same path. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + */ + if (!apply_child_basequals(root, parent, childrel, childRTE, appinfo) || + relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; } /* @@ -597,7 +836,7 @@ create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) * column, filling placeholder entries for dropped columns, etc., using * the child's TupleDesc. */ - tlist = preprocess_targetlist(subroot); + tlist = preprocess_targetlist(subroot, true); subroot->processed_tlist = tlist; /* @@ -619,3 +858,131 @@ create_inherited_target_child_root(PlannerInfo *root, AppendRelInfo *appinfo) return subroot; } + +/* + * apply_child_basequals + * Populate childrel's quals based on rel's quals, translating them using + * appinfo. + * + * If any of the resulting clauses evaluate to false or NULL, we return false + * and don't apply any quals. Caller can mark the relation as a dummy rel in + * this case, since it needn't be scanned. + * + * If any resulting clauses evaluate to true, they're unnecessary and we don't + * apply then. + */ +static bool +apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *childrel, RangeTblEntry *childRTE, + AppendRelInfo *appinfo) +{ + List *childquals; + Index cq_min_security; + ListCell *lc; + + /* + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities for + * const-simplification, and perhaps even pseudoconstant quals. Therefore, + * transform each RestrictInfo separately to see if it reduces to a + * constant or pseudoconstant. (We must process them separately to keep + * track of the security level of each qual.) + */ + childquals = NIL; + cq_min_security = UINT_MAX; + foreach(lc, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, + (Node *) rinfo->clause, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + return false; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + /* track minimum security level among child quals */ + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might have + * securityQuals associated with this particular child node. (Currently + * this can only happen in appendrels originating from UNION ALL; + * inheritance child tables don't have their own securityQuals, see + * expand_inherited_rtentry().) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except that we + * can't make any general deductions from such quals, since they don't + * hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* not likely that we'd see constants here, so no check */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, false, false, + security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* + * OK, we've got all the baserestrictinfo quals for this child. + */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + return true; +} diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index d8c1981b4d..83c6a956b7 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -107,20 +107,20 @@ static void set_baserel_partition_key_exprs(Relation relation, * important for it. */ void -get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) +get_relation_info(PlannerInfo *root, RangeTblEntry *rte, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; bool hasindex; List *indexinfos = NIL; + bool inhparent = rte->inh; /* * We need not lock the relation since it was already locked, either by * the rewriter or when expand_inherited_rtentry() added it to the query's * rangetable. */ - relation = table_open(relationObjectId, NoLock); + relation = heap_open(rte->relid, NoLock); /* Temporary and unlogged relations are inaccessible during recovery. */ if (!RelationNeedsWAL(relation) && RecoveryInProgress()) @@ -446,11 +446,32 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, get_relation_foreign_keys(root, rel, relation, inhparent); /* - * Collect info about relation's partitioning scheme, if any. Only - * inheritance parents may be partitioned. + * Collect some additional information for inheritance parents. */ - if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - set_relation_partition_info(root, rel, relation); + if (inhparent) + { + /* + * We'll need the TupleDesc when initializing the child relation. + * A copy is being made because concurrent changes might drop + * the relcache entry. That's possible because ALTER TABLE + * child_table NO INHERIT parent_table only requires an + * AccessShareLock on parent_table. + */ + rel->tupdesc = CreateTupleDescCopy(RelationGetDescr(relation)); + rel->reltype = RelationGetForm(relation)->reltype; + + /* + * If partitioned, also save the information of partitioning scheme, + * and whether the query updates any of the partition key columns. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + set_relation_partition_info(root, rel, relation); + root->partColsUpdated |= has_partition_attrs(relation, + rte->updatedCols, + NULL); + } + } table_close(relation, NoLock); @@ -460,7 +481,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * removing an index, or adding a hypothetical index to the indexlist. */ if (get_relation_info_hook) - (*get_relation_info_hook) (root, relationObjectId, inhparent, rel); + (*get_relation_info_hook) (root, rte->relid, rte->inh, rel); } /* @@ -2027,16 +2048,20 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; + + /* + * Since we must've taken a lock on the table, it's okay to simply copy + * the pointers to relcache data here. + */ + rel->part_oids = partdesc->oids; + rel->boundinfo = partdesc->boundinfo; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 4130514952..f6fca75677 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -132,6 +132,50 @@ setup_append_rel_array(PlannerInfo *root) } /* + * expand_planner_arrays + * Expand the PlannerInfo arrays by add_size members and initialize the + * the newly added bytes with zero + */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size; + + Assert(add_size > 0); + + new_size = root->simple_rel_array_size + add_size; + + /* Expand various arrays and 0-initialize added bytes. */ + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + + if (root->append_rel_array) + { + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + } + else + { + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + } + + root->simple_rel_array_size = new_size; +} + +/* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. */ @@ -195,6 +239,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->joininfo = NIL; rel->has_eclass_joins = false; rel->consider_partitionwise_join = false; /* might get changed later */ + rel->top_parent_relids = NULL; /* might be changed later */ rel->part_scheme = NULL; rel->nparts = 0; rel->boundinfo = NULL; @@ -203,6 +248,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->partexprs = NULL; rel->nullable_partexprs = NULL; rel->partitioned_child_rels = NIL; + rel->inh_root_parent = 0; /* might be changed later */ /* * Pass top parent's relids down the inheritance hierarchy. If the parent @@ -216,16 +262,25 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->top_parent_relids = parent->top_parent_relids; else rel->top_parent_relids = bms_copy(parent->relids); + + /* + * For inheritance child relations, we also set inh_root_parent. + * Note that 'parent' might itself be a child (a sub-partitioned + * partition), in which case we simply use its value of + * inh_root_parent. + */ + if (parent->rtekind == RTE_RELATION) + rel->inh_root_parent = parent->inh_root_parent > 0 ? + parent->inh_root_parent : + parent->relid; } - else - rel->top_parent_relids = NULL; /* Check type of rtable entry */ switch (rte->rtekind) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte, rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -273,52 +328,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); - /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. - */ - if (rte->inh) - { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); - - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; - } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); - } - return rel; } diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 8c9721935d..472a6cd331 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -45,6 +45,8 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "partitioning/partprune.h" @@ -433,17 +435,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -537,23 +545,20 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ @@ -561,6 +566,13 @@ prune_append_rel_partitions(RelOptInfo *rel) return NULL; /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. */ @@ -587,15 +599,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index b219ff8806..339b786f20 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -15,6 +15,7 @@ #define PATHNODES_H #include "access/sdir.h" +#include "access/tupdesc.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/params.h" @@ -365,7 +366,6 @@ struct PlannerInfo }; - /* * In places where it's known that simple_rte_array[] must have been prepared * already, we just index into it to fetch RTEs. In code that might be @@ -718,11 +718,28 @@ typedef struct RelOptInfo int nparts; /* number of partitions */ struct PartitionBoundInfoData *boundinfo; /* Partition bounds */ List *partition_qual; /* partition constraint */ + Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ + + /* + * For inheritance children, this is the RT index of inheritance table + * mentioned in the query from which this relation originated. + * top_parent_relids cannot be used for this, because if the inheritance + * root table is itself under UNION ALL, top_parent_relids contains the + * RT index of UNION ALL parent subquery. + */ + Index inh_root_parent; + + /* + * Set only if this is an inheritance parent relation. This information + * is needed when initializing the planning info for children. + */ + TupleDesc tupdesc; /* A "copy" of the table's tuple desriptor */ + Oid reltype; /* Table's reltype */ } RelOptInfo; /* diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h index 790914c1b0..c0b8db9a62 100644 --- a/src/include/optimizer/appendinfo.h +++ b/src/include/optimizer/appendinfo.h @@ -17,9 +17,10 @@ #include "nodes/pathnodes.h" #include "utils/relcache.h" -extern AppendRelInfo *make_append_rel_info(Relation parentrel, - Relation childrel, - Index parentRTindex, Index childRTindex); +extern AppendRelInfo *make_append_rel_info(RelOptInfo *parent, + RangeTblEntry *parentrte, + TupleDesc childdesc, Oid childoid, Oid childtype, + Index childRTindex); extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos); extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node, diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 574bb85b50..3a803b3fd0 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -277,6 +277,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index c337f047cb..04731f532f 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -25,8 +25,8 @@ typedef void (*get_relation_info_hook_type) (PlannerInfo *root, extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; -extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); +extern void get_relation_info(PlannerInfo *root, RangeTblEntry *rte, + RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index a9b2c9026c..27de05ba3e 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -34,7 +34,8 @@ extern Relids get_relids_for_join(Query *query, int joinrelid); /* * prototypes for preptlist.c */ -extern List *preprocess_targetlist(PlannerInfo *root); +extern List *preprocess_targetlist(PlannerInfo *root, + bool inheritance_expanded); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 6bc106831e..1450cef057 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) -- 2.11.0
0004-Teach-planner-to-only-process-unpruned-partitions.patch (text/plain, 6.8K) download From f3d0fa8072550b66739d445d9b5fb67c8ad1e923 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 15 Oct 2018 10:59:24 +0900 Subject: [PATCH 4/5] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/inherit.c | 1 + src/backend/optimizer/util/relnode.c | 3 +++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/pathnodes.h | 4 ++++ 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 6f77d2a0f4..fdbf7689ed 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1452,6 +1452,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index ce0ced792f..2feb5d5b2d 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6857,7 +6857,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -6865,9 +6867,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -7009,7 +7009,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -7021,7 +7020,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -7031,9 +7032,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 492757fd8c..6e9564d0ce 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -365,6 +365,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, /* Perform pruning. */ partindexes = prune_append_rel_partitions(parentrel); + parentrel->live_parts = partindexes; /* Must expand PlannerInfo arrays before we can add children. */ if (bms_num_members(partindexes) > 0) diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index f6fca75677..03c9ded294 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1743,6 +1743,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 472a6cd331..babb7d3406 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -438,29 +438,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 339b786f20..c3103370ca 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -721,6 +721,10 @@ typedef struct RelOptInfo Oid *part_oids; /* partition OIDs */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Indexes into part_rels of the non-NULL + * RelOptInfos of unpruned partitions; exists + * to avoid having to iterate over the entire + * part_rels array to filter NULL entries. */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.11.0
0005-Do-not-lock-all-partitions-at-the-beginning.patch (text/plain, 1.9K) download From 7e56837161cc139008d9a945a89daf513267b299 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 12 Oct 2018 19:12:53 +0900 Subject: [PATCH 5/5] Do not lock all partitions at the beginning --- src/backend/optimizer/util/inherit.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 6e9564d0ce..85770226d0 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -332,10 +332,6 @@ expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, * * Partitions are added to the query in order in which they are found in * the parent's PartitionDesc. - * - * Note: even though only the unpruned partitions will be added to the - * resulting plan, this still locks *all* partitions via find_all_inheritors - * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, @@ -349,10 +345,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, parentrel->inh_root_parent : parentRTindex; - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - /* * Initialize partitioned_child_rels to contain this RT index. * @@ -389,8 +381,11 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, RangeTblEntry *childrte; Index childRTindex; - /* Open rel; we already have required locks */ - newrelation = table_open(childOID, NoLock); + /* + * Open rel; this's the first time of opening partitions for this + * query, so take the appropriate locks. + */ + newrelation = table_open(childOID, lockmode); Assert(!RELATION_IS_OTHER_TEMP(newrelation)); /* -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-19 17:07  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 1 reply; 265+ messages in thread

From: Tom Lane @ 2019-02-19 17:07 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Amit Langote <amitlangote09@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> writes:
> On 2019/02/19 4:42, Tom Lane wrote:
>> I don't much care for re-calling build_base_rel_tlists to add extra
>> Vars to the appropriate relations; 99% of the work it does will be
>> wasted, and with enough child rels you could run into an O(N^2)
>> problem.  Maybe you could call add_vars_to_targetlist directly,
>> since you know exactly what Vars you're adding?

> Assuming you're talking about the build_base_rel_tlists() call in
> create_inherited_target_child_root(), it's necessary because *all* tlist
> Vars are new, because the targetlist has just been translated at that
> point.  But maybe I missed your point?

Hmm, I'll take a closer look --- I thought it was just there to add the
new ctid-or-equivalent columns.  Didn't our earlier translation of the
whole subroot structure reach the reltargetlists?

> By the way, later patch in the series will cause partition pruning to
> occur before these child PlannerInfos are generated, so
> create_inherited_target_child_root will be called only as many times as
> there are un-pruned child target relations.

Well, that helps, but only for "point update" queries.  You still need
to be wary of not causing O(N^2) behavior when there are lots of unpruned
partitions.

>> What is the point of moving the calculation of all_baserels?  The earlier
>> you construct that, the more likelihood that code will have to be written
>> to modify it (like, say, what you had to put into
>> create_inherited_target_child_root), and I do not see anything in this
>> patch series that needs it to be available earlier.

> all_baserels needs to be built in original PlannerInfo before child
> PlannerInfos are constructed, so that they can simply copy it and have the
> parent target baserel RT index in it replaced by child target baserel RT
> index.  set_inherited_target_rel_sizes/pathlists use child PlannerInfos,
> so all_baserels must be set in them just like it is in the original
> PlannerInfo.

No, I think you're not getting my point: if you build all_baserels in
the same place where it already is being built, you don't need to do
any of that because it's already correct.  It looks to me like this
code motion is left over from some earlier iteration of the patch where
it probably was necessary, but now it's just making your life harder.

>> Couldn't inh_target_child_rels list be removed in favor of looking
>> at the relid fields of the inh_target_child_path_rels entries?
>> Having to keep those two lists in sync seems messy.

> Don't like having two lists either, but inh_target_child_path_rels entries
> can be RELOPT_JOINREL, so relid can be 0.

So?  For a join, there's no particularly relevant integer to put into
inh_target_child_rels either.  (I might like these two lists better
if they had better-chosen names.  Merely making them long enough to
induce carpal tunnel syndrome isn't helping distinguish them.)

> Attached updated patches.  0001 that I'd previously posted is no longer
> included, as I said in the other email.

OK, I'll make another pass over 0001 today.

			regards, tom lane




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-19 20:57  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 1 reply; 265+ messages in thread

From: Tom Lane @ 2019-02-19 20:57 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Amit Langote <amitlangote09@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

I wrote:
> OK, I'll make another pass over 0001 today.

So I started the day with high hopes for this, but the more I looked at
it the less happy I got, and finally I ran into something that looks to
be a complete show-stopper.  Namely, that the patch does not account
for the possibility of an inherited target rel being the outside for a
parameterized path to some other rel.  Like this example in the
regression database:

explain update a set aa = aa + 1
from tenk1 t where a.aa = t.unique2;

With HEAD, you get a perfectly nice plan that consists of an append
of per-child plans like this:

   ->  Nested Loop  (cost=0.29..8.31 rows=1 width=16)
         ->  Seq Scan on a  (cost=0.00..0.00 rows=1 width=10)
         ->  Index Scan using tenk1_unique2 on tenk1 t  (cost=0.29..8.30 rows=1 
width=10)
               Index Cond: (unique2 = a.aa)

With the 0001 patch, this gets an Assert during set_base_rel_pathlists,
because indxpath.c tries to make a parameterized path for tenk1
with "a" as the outer rel.  Since tenk1's joinlist hasn't been
touched, it's still referencing the inheritance parent, and the
code notices that we haven't made a rowcount estimate for that.
Even if we had, we'd generate a Path referencing Vars of the parent
rel, which would not work.

Conceivably, such a Path could be fixed up later (say by applying
adjust_appendrel_attrs to it during createplan.c), but that is not
going to fix the fundamental problem: the cost estimate for such a
Path should vary depending on how large we think the outer rel is,
and we don't have a reasonable way to set that if we're trying to
make a one-size-fits-all Path for something that's being joined to
an inheritance tree with a widely varying set of relation sizes.

So I do not see any way to make this approach work without a
significant(?) sacrifice in the quality of plans.

I've got other issues with the patch too, but it's probably not
worth getting into them unless we can answer this objection.

			regards, tom lane




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-20 05:47  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 1 reply; 265+ messages in thread

From: Amit Langote @ 2019-02-20 05:47 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Amit Langote <amitlangote09@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On 2019/02/20 5:57, Tom Lane wrote:
> I wrote:
>> OK, I'll make another pass over 0001 today.
> 
> So I started the day with high hopes for this, but the more I looked at
> it the less happy I got, and finally I ran into something that looks to
> be a complete show-stopper.  Namely, that the patch does not account
> for the possibility of an inherited target rel being the outside for a
> parameterized path to some other rel.  Like this example in the
> regression database:
> 
> explain update a set aa = aa + 1
> from tenk1 t where a.aa = t.unique2;
> 
> With HEAD, you get a perfectly nice plan that consists of an append
> of per-child plans like this:
> 
>    ->  Nested Loop  (cost=0.29..8.31 rows=1 width=16)
>          ->  Seq Scan on a  (cost=0.00..0.00 rows=1 width=10)
>          ->  Index Scan using tenk1_unique2 on tenk1 t  (cost=0.29..8.30 rows=1 
> width=10)
>                Index Cond: (unique2 = a.aa)
> 
> With the 0001 patch, this gets an Assert during set_base_rel_pathlists,
>
> because indxpath.c tries to make a parameterized path for tenk1
> with "a" as the outer rel.  Since tenk1's joinlist hasn't been
> touched, it's still referencing the inheritance parent, and the
> code notices that we haven't made a rowcount estimate for that.

Hmm, yeah.  It wouldn't have crashed with an earlier version of the patch,
because with it, we were setting the parent relation's rows to a dummy
value of 1 at the end of set_inherited_target_rel_sizes, which I removed
in the latest patch after your comment upthread.

> Even if we had, we'd generate a Path referencing Vars of the parent
> rel, which would not work.
>
> Conceivably, such a Path could be fixed up later (say by applying
> adjust_appendrel_attrs to it during createplan.c),

Actually, reparameterize_path_by_child (invented by partitionwise-join
commit) seems to take care of fixing up the Path to have child attributes,
so the plan comes out exactly as on HEAD.  But to be honest, that means
this new approach of inherited update join planning only appears to work
by accident.

> but that is not
> going to fix the fundamental problem: the cost estimate for such a
> Path should vary depending on how large we think the outer rel is,
> and we don't have a reasonable way to set that if we're trying to
> make a one-size-fits-all Path for something that's being joined to
> an inheritance tree with a widely varying set of relation sizes.

What if we set the parent target relation's rows to an average of child
target relation's rows, that is, instead of setting it to dummy 1 that
previous versions of the patches were doing?

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-20 15:50  Tom Lane <tgl@sss.pgh.pa.us>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 1 reply; 265+ messages in thread

From: Tom Lane @ 2019-02-20 15:50 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Amit Langote <amitlangote09@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> writes:
> On 2019/02/20 5:57, Tom Lane wrote:
>> but that is not
>> going to fix the fundamental problem: the cost estimate for such a
>> Path should vary depending on how large we think the outer rel is,
>> and we don't have a reasonable way to set that if we're trying to
>> make a one-size-fits-all Path for something that's being joined to
>> an inheritance tree with a widely varying set of relation sizes.

> What if we set the parent target relation's rows to an average of child
> target relation's rows, that is, instead of setting it to dummy 1 that
> previous versions of the patches were doing?

Well, if somebody were holding a gun to our collective heads and saying
you must do inherited UPDATE/DELETE this way, we could probably limp along
with that; or maybe it'd be better to use the sum of the children's row
counts.  That depends on how many of the per-child join plans end up using
the parameterized path, which is something we couldn't hope to guess so
early.  (Arguably, the way the code is now, it's overestimating the true
costs of such paths, since it doesn't account for different child plans
possibly using the same indexscan and thereby getting caching benefits.)
In any case there'd be side-effects on code that currently expects
appendrels to have size zero, eg the total_table_pages calculation in
make_one_rel.

However, there are other reasons why I'm not really happy with the
approach proposed in this patch.

The main problem is that cloning the PlannerInfo while still sharing a lot
of infrastructure between the clones is a horrid hack that I think will be
very buggy and unmaintainable.  We've gotten away with it so far in
inheritance_planner because (a) the complexity is all local to that
function and (b) the cloning happens very early in the planning process,
so that there's not much shared subsidiary data to worry about; mostly
just the parse tree, which in fact isn't shared because the first thing
we do is push it through adjust_appendrel_attrs.  This patch proposes
to clone much later, and both the actual cloning and the consequences
of that are spread all over, and I don't think we're nearly done with
the consequences :-(.  I found the parameterized-path problem while
wondering why it was okay to not worry about adjusting attrs in data
structures used during path construction for other baserels ... turns
out it isn't.  There's a lot of other stuff in PlannerInfo that you're
not touching, for instance pathkeys and placeholders; and I'm afraid
much of that represents either current bugs or future problems.

So what I feel we should do is set this aside for now and see if we
can make something of the other idea I proposed.  If we could get
rid of expand-inheritance-at-the-top altogether, and plan inherited
UPDATE/DELETE the same as inherited SELECT, that would be a large
reduction in planner complexity, hence much to be preferred over this
approach which is a large increase in planner complexity.  If that
approach crashes and burns, we can come back to this.

There might be parts of this work we can salvage, though.  It seems
like the idea of postponing expand_inherited_tables() might be
something we could use anyway.

			regards, tom lane




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-21 02:31  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 1 reply; 265+ messages in thread

From: Amit Langote @ 2019-02-21 02:31 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Amit Langote <amitlangote09@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On 2019/02/21 0:50, Tom Lane wrote:
> However, there are other reasons why I'm not really happy with the
> approach proposed in this patch.
> 
> The main problem is that cloning the PlannerInfo while still sharing a lot
> of infrastructure between the clones is a horrid hack that I think will be
> very buggy and unmaintainable.  We've gotten away with it so far in
> inheritance_planner because (a) the complexity is all local to that
> function and (b) the cloning happens very early in the planning process,
> so that there's not much shared subsidiary data to worry about; mostly
> just the parse tree, which in fact isn't shared because the first thing
> we do is push it through adjust_appendrel_attrs.  This patch proposes
> to clone much later, and both the actual cloning and the consequences
> of that are spread all over, and I don't think we're nearly done with
> the consequences :-(.  I found the parameterized-path problem while
> wondering why it was okay to not worry about adjusting attrs in data
> structures used during path construction for other baserels ... turns
> out it isn't.  There's a lot of other stuff in PlannerInfo that you're
> not touching, for instance pathkeys and placeholders; and I'm afraid
> much of that represents either current bugs or future problems.
> 
> So what I feel we should do is set this aside for now and see if we
> can make something of the other idea I proposed.  If we could get
> rid of expand-inheritance-at-the-top altogether, and plan inherited
> UPDATE/DELETE the same as inherited SELECT, that would be a large
> reduction in planner complexity, hence much to be preferred over this
> approach which is a large increase in planner complexity.  If that
> approach crashes and burns, we can come back to this.

OK, I agree that the other approach might be a better way forward.  It'll
not just improve the performance in an elegant manner, but will also make
other projects more feasible, such as, MERGE, what Fujita-san mentioned on
the other thread, etc.

> There might be parts of this work we can salvage, though.  It seems
> like the idea of postponing expand_inherited_tables() might be
> something we could use anyway.

+1.  So, let's try to do things in this order:

1. Make inheritance-expansion-at-bottom case perform better now,
addressing at least SELECT performance in PG 12, provided we manage to get
the patches in order in time (I'll try to post the updated
lazy-inheritance-expansion patch later this week.)

2. Overhaul inherited UPDATE/DELETE planning to use
inheritance-expansion-at-bottom (PG 13)

It's unfortunate that UPDATE/DELETE won't perform as well as SELECTs even
couple of releases after declarative partitioning was introduced, but I
agree that we should solve the underlying issues in an elegant way.

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-22 12:45  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 1 reply; 265+ messages in thread

From: Amit Langote @ 2019-02-22 12:45 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: David Rowley <david.rowley@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Amit Langote <amitlangote09@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On 2019/02/21 11:31, Amit Langote wrote:
> On 2019/02/21 0:50, Tom Lane wrote:
>> There might be parts of this work we can salvage, though.  It seems
>> like the idea of postponing expand_inherited_tables() might be
>> something we could use anyway.
> 
> +1.  So, let's try to do things in this order:
> 
> 1. Make inheritance-expansion-at-bottom case perform better now,
> addressing at least SELECT performance in PG 12, provided we manage to get
> the patches in order in time (I'll try to post the updated
> lazy-inheritance-expansion patch later this week.)

I have updated the inheritance expansion patch.

Patch 0001 rewrites optimizer/utils/inherit.c, so that it allows
inheritance expansion to be invoked from make_one_rel().  Although the
rewrite in this version of the patch is a bit different from earlier
versions, because I needed to account for the fact that
inheritance_planner (whose rewrite I'm withdrawing) will use the same
expansion code to expand target inheritance.  So, the code now needs to
treat source-inheritance-expansion and target-inheritance-expansion cases
a bit differently.

I wanted to polish the code and various comments a bit more because the
rewritten expansion code looks different from earlier versions as I
mentioned above, but I needed to rush out today due to a family emergency
and won't be able to reply until Wednesday next week.  Sorry.

Thanks,
Amit

Attachments: download all (3 files, .tar.gz)

v23-0001-Lazy-creation-of-RTEs-for-inheritance-children.patch (text/plain, 97.4K) download From be68728ebf315fb7cdb05fe2ace3db8c0346a7db Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 26 Oct 2018 16:45:59 +0900 Subject: [PATCH v23 1/3] Lazy creation of RTEs for inheritance children Currently, expand_inherited_tables called from subquery_planner opens all inheritance child tables and adds them to the query's range table and PlannerInfo in the form of AppendRelInfo's initially and later RelOptInfo's. For partitioning, that's pretty wasteful, because it's possible to determine only the partitions that will need to be scanned using partition pruning, which doesn't require opening the partitions themselves. However, the point at which expand_inherited_tables is called currently is too early to perform partition pruning. This commit rearranges things within the planner so that expand_inherited_tables can be called from query_planner after the latter has finished setting up the information needed to perform partition pruning. However that means the PlannerInfo arrays that store RangeTblEntry's, RelOptInfo's, and AppendRelInfo's need to be expanded if inheritance expansion leads to adding more relations to planning. There are also consequences around targetlist expansion. For example, preprocess_targetlist cannot add the junk columns entries needed for row mark handling until all of the inheritance children have been determined, because different children may need different types of junk columns to be added. So, the approach of late inheritance expansion required some interface tweaks for preprocess_targetlist(). For partitioning, although we don't create a RangeTblEntry and RelOptInfo for pruned partitions at make_one_rel time, partitionwise join code relies on the fact that even though partitions may have been pruned, they'd still own a RelOptInfo to handle the outer join case where the pruned partition appears on the nullable side of join. Partitionwise join code deals with that by allocating dummy RelOptInfos for pruned partitions that are based mostly on their parent's properties. There are some regression test diffs: 1. Caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child, as seen in the partition_aggregate.out test output. 2. Those in postgres_fdw.out are caused by the fact that junk columns required for row marking are added to reltarget->exprs later than user columns, because the row marking junk columns aren't added until the inheritance is expanded which as of this commit is later than it used to be as noted above. --- contrib/postgres_fdw/expected/postgres_fdw.out | 70 +- src/backend/optimizer/path/allpaths.c | 212 +---- src/backend/optimizer/path/joinrels.c | 92 ++- src/backend/optimizer/plan/initsplan.c | 49 -- src/backend/optimizer/plan/planner.c | 190 +++-- src/backend/optimizer/plan/setrefs.c | 6 + src/backend/optimizer/prep/preptlist.c | 15 +- src/backend/optimizer/prep/prepunion.c | 3 + src/backend/optimizer/util/inherit.c | 918 ++++++++++++++++------ src/backend/optimizer/util/plancat.c | 18 +- src/backend/optimizer/util/relnode.c | 95 ++- src/backend/partitioning/partprune.c | 44 +- src/include/nodes/pathnodes.h | 1 + src/include/optimizer/inherit.h | 2 + src/include/optimizer/pathnode.h | 1 + src/include/optimizer/plancat.h | 4 +- src/include/optimizer/prep.h | 3 +- src/test/regress/expected/partition_aggregate.out | 4 +- src/test/regress/expected/partition_prune.out | 12 +- src/test/regress/expected/rowsecurity.out | 16 +- 20 files changed, 1067 insertions(+), 688 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 42108bd3d4..4d9029911a 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7128,15 +7128,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7166,15 +7166,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7190,8 +7190,8 @@ select * from bar where f1 in (select f1 from foo) for share; -- Check UPDATE with inherited target and an inherited source table explain (verbose, costs off) update bar set f2 = f2 + 100 where f1 in (select f1 from foo); - QUERY PLAN ---------------------------------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------------------------------- Update on public.bar Update on public.bar Foreign Update on public.bar2 @@ -7203,33 +7203,33 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join - Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid + Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo_1.ctid, foo_1.*, foo_1.tableoid Inner Unique: true - Hash Cond: (bar2.f1 = foo.f1) + Hash Cond: (bar2.f1 = foo_1.f1) -> Foreign Scan on public.bar2 Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo_1.f1, foo_1.ctid, foo_1.*, foo_1.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 - Group Key: foo.f1 + Output: foo_1.f1, foo_1.ctid, foo_1.*, foo_1.tableoid + Group Key: foo_1.f1 -> Append - -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 - -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + -> Seq Scan on public.foo foo_1 + Output: foo_1.f1, foo_1.ctid, foo_1.*, foo_1.tableoid + -> Foreign Scan on public.foo2 foo2_1 + Output: foo2_1.f1, foo2_1.ctid, foo2_1.*, foo2_1.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (39 rows) @@ -7276,8 +7276,8 @@ where bar.f1 = ss.f1; -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Merge Join - Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, (ROW(foo.f1)) - Merge Cond: (bar2.f1 = foo.f1) + Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, (ROW(foo_2.f1)) + Merge Cond: (bar2.f1 = foo_2.f1) -> Sort Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Sort Key: bar2.f1 @@ -7285,18 +7285,18 @@ where bar.f1 = ss.f1; Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Sort - Output: (ROW(foo.f1)), foo.f1 - Sort Key: foo.f1 + Output: (ROW(foo_2.f1)), foo_2.f1 + Sort Key: foo_2.f1 -> Append - -> Seq Scan on public.foo - Output: ROW(foo.f1), foo.f1 - -> Foreign Scan on public.foo2 - Output: ROW(foo2.f1), foo2.f1 + -> Seq Scan on public.foo foo_2 + Output: ROW(foo_2.f1), foo_2.f1 + -> Foreign Scan on public.foo2 foo2_2 + Output: ROW(foo2_2.f1), foo2_2.f1 Remote SQL: SELECT f1 FROM public.loct1 - -> Seq Scan on public.foo foo_1 - Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3) - -> Foreign Scan on public.foo2 foo2_1 - Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3) + -> Seq Scan on public.foo foo_3 + Output: ROW((foo_3.f1 + 3)), (foo_3.f1 + 3) + -> Foreign Scan on public.foo2 foo2_3 + Output: ROW((foo2_3.f1 + 3)), (foo2_3.f1 + 3) Remote SQL: SELECT f1 FROM public.loct1 (45 rows) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 0debac75c6..f132abe57d 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -44,7 +44,6 @@ #include "optimizer/tlist.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -138,9 +137,6 @@ static void subquery_push_qual(Query *subquery, static void recurse_push_qual(Node *setOp, Query *topquery, RangeTblEntry *rte, Index rti, Node *qual); static void remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel); -static bool apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, - RelOptInfo *childrel, - RangeTblEntry *childRTE, AppendRelInfo *appinfo); /* @@ -156,6 +152,16 @@ make_one_rel(PlannerInfo *root, List *joinlist) double total_pages; /* + * Expand RT entries that represent inherited or partitioned tables. + * This will perform partition pruning on partitioned tables in the + * original range table and also recursively on any child partitioned + * tables that were added by the expansion of the original parent(s). + * As new entries are added to the range table, various arrays in the + * PlannerInfo will be expanded accordingly. + */ + expand_inherited_tables(root); + + /* * Construct the all_baserels Relids set. */ root->all_baserels = NULL; @@ -945,8 +951,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -954,32 +958,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Assert(IS_SIMPLE_REL(rel)); /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, we will bubble up - * the indexes of partitioned relations that appear down in the tree, so - * that when we've created Paths for all the children, the root - * partitioned table's list will contain all such indexes. - */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - rel->partitioned_child_rels = list_make1_int(rti); - - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - - /* * If this is a partitioned baserel, set the consider_partitionwise_join * flag; currently, we only consider partitionwise joins with the baserel * if its targetlist doesn't contain a whole-row Var. @@ -1025,50 +1003,26 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); + Assert(childrel != NULL); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - /* - * 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 - * baserestrictinfo quals. + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals or quals that contradict its + * constraints. */ - if (!apply_child_basequals(root, rel, childrel, childRTE, appinfo)) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); + if (IS_DUMMY_REL(childrel)) continue; - } - - if (relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* - * This child need not be scanned, so we can omit it from the - * appendrel. - */ - set_dummy_rel_pathlist(childrel); - continue; - } /* - * CE failed, so finish copying/modifying targetlist and join quals. + * Copy/Modify targetlist. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -3557,134 +3511,6 @@ generate_partitionwise_join_paths(PlannerInfo *root, RelOptInfo *rel) list_free(live_children); } -/* - * apply_child_basequals - * Populate childrel's quals based on rel's quals, translating them using - * appinfo. - * - * If any of the resulting clauses evaluate to false or NULL, we return false - * and don't apply any quals. Caller can mark the relation as a dummy rel in - * this case, since it needn't be scanned. - * - * If any resulting clauses evaluate to true, they're unnecessary and we don't - * apply then. - */ -static bool -apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, - RelOptInfo *childrel, RangeTblEntry *childRTE, - AppendRelInfo *appinfo) -{ - List *childquals; - Index cq_min_security; - ListCell *lc; - - /* - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities for - * const-simplification, and perhaps even pseudoconstant quals. Therefore, - * transform each RestrictInfo separately to see if it reduces to a - * constant or pseudoconstant. (We must process them separately to keep - * track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - return false; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. (Currently - * this can only happen in appendrels originating from UNION ALL; - * inheritance child tables don't have their own securityQuals, see - * expand_inherited_rtentry().) Pull any such securityQuals up into the - * baserestrictinfo for the child. This is similar to - * process_security_barrier_quals() for the parent rel, except that we - * can't make any general deductions from such quals, since they don't - * hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - return true; -} - /***************************************************************************** * DEBUG SUPPORT *****************************************************************************/ diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index dfbbfdac6d..5047b7b1df 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -14,6 +14,7 @@ */ #include "postgres.h" +#include "access/table.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" @@ -21,6 +22,8 @@ #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" +#include "optimizer/tlist.h" +#include "parser/parsetree.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -52,6 +55,9 @@ static SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, Relids left_relids, Relids right_relids); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, Relation parentrel, + int partidx); /* @@ -1318,6 +1324,8 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, RelOptInfo *joinrel, SpecialJoinInfo *parent_sjinfo, List *parent_restrictlist) { + Relation baserel1 = NULL, + baserel2 = NULL; bool rel1_is_simple = IS_SIMPLE_REL(rel1); bool rel2_is_simple = IS_SIMPLE_REL(rel2); int nparts; @@ -1368,6 +1376,18 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, nparts = joinrel->nparts; + if (rel1_is_simple) + { + RangeTblEntry *rte = planner_rt_fetch(rel1->relid, root); + + baserel1 = table_open(rte->relid, NoLock); + } + if (rel2_is_simple) + { + RangeTblEntry *rte = planner_rt_fetch(rel2->relid, root); + + baserel2 = table_open(rte->relid, NoLock); + } /* * Create child-join relations for this partitioned join, if those don't * exist. Add paths to child-joins for a pair of child relations @@ -1384,6 +1404,13 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (IS_SIMPLE_REL(rel1) && child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, baserel1, + cnt_parts); + if (IS_SIMPLE_REL(rel1) && child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, baserel2, + cnt_parts); + /* * If a child table has consider_partitionwise_join=false, it means * that it's a dummy relation for which we skipped setting up tlist @@ -1444,6 +1471,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, child_joinrel, child_sjinfo, child_restrictlist); } + + if (baserel1) + table_close(baserel1, NoLock); + if (baserel2) + table_close(baserel2, NoLock); } /* @@ -1462,8 +1494,14 @@ update_child_rel_info(PlannerInfo *root, (Node *) rel->reltarget->exprs, 1, &appinfo); - /* Make child entries in the EquivalenceClass as well */ - if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) + /* + * Make child entries in the EquivalenceClass as well. If the childrel + * appears to be a dummy one (one built by build_dummy_partition_rel()), + * no need to make any new entries, because anything that'd need those + * can instead use the parent's (rel). + */ + if (childrel->relid != rel->relid && + (rel->has_eclass_joins || has_useful_pathkeys(root, rel))) add_child_rel_equivalences(root, appinfo, rel, childrel); childrel->has_eclass_joins = rel->has_eclass_joins; } @@ -1674,3 +1712,53 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +static RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, + Relation parentrel, int partidx) +{ + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + AppendRelInfo *appinfo = make_append_rel_info(parentrel, parentrel, + parent->relid, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 2afc3f1dfe..5e8a8b1eee 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -419,7 +419,6 @@ void create_lateral_join_info(PlannerInfo *root) { bool found_laterals = false; - Relids prev_parents PG_USED_FOR_ASSERTS_ONLY = NULL; Index rti; ListCell *lc; @@ -618,54 +617,6 @@ create_lateral_join_info(PlannerInfo *root) bms_add_member(brel2->lateral_referencers, rti); } } - - /* - * Lastly, propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - * - * It's possible for child rels to have their own children, in which case - * the topmost parent's lateral info must be propagated all the way down. - * This code handles that case correctly so long as append_rel_list has - * entries for child relationships before grandchild relationships, which - * is an okay assumption right now, but we'll need to be careful to - * preserve it. The assertions below check for incorrect ordering. - */ - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); - RelOptInfo *parentrel = root->simple_rel_array[appinfo->parent_relid]; - RelOptInfo *childrel = root->simple_rel_array[appinfo->child_relid]; - - /* - * If we're processing a subquery of a query with inherited target rel - * (cf. inheritance_planner), append_rel_list may contain entries for - * tables that are not part of the current subquery and hence have no - * RelOptInfo. Ignore them. We can ignore dead rels, too. - */ - if (parentrel == NULL || !IS_SIMPLE_REL(parentrel)) - continue; - - /* Verify that children are processed before grandchildren */ -#ifdef USE_ASSERT_CHECKING - prev_parents = bms_add_member(prev_parents, appinfo->parent_relid); - Assert(!bms_is_member(appinfo->child_relid, prev_parents)); -#endif - - /* OK, propagate info down */ - Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - Assert(childrel->direct_lateral_relids == NULL); - childrel->direct_lateral_relids = parentrel->direct_lateral_relids; - Assert(childrel->lateral_relids == NULL); - childrel->lateral_relids = parentrel->lateral_relids; - Assert(childrel->lateral_referencers == NULL); - childrel->lateral_referencers = parentrel->lateral_referencers; - } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 5579dfa65e..bb1631b68e 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -25,6 +25,7 @@ #include "access/table.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -132,6 +133,9 @@ static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static void inheritance_planner(PlannerInfo *root); static void grouping_planner(PlannerInfo *root, bool inheritance_update, double tuple_fraction); +static Path *create_single_target_modifytable_path(PlannerInfo *root, + RelOptInfo *final_rel, + Path *subpath); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, int *tleref_to_colnum_map); @@ -714,27 +718,24 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } /* * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). + * pullup (so that all non-inherited RTEs are present). */ preprocess_rowmarks(root); /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain RTE_RELATION entries, so it's OK (and marginally more efficient) - * to do it after checking for joins and other special RTEs. We must do - * this after pulling up subqueries, else we'd fail to handle inherited - * tables in subqueries. - */ - expand_inherited_tables(root); - - /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -1213,6 +1214,15 @@ inheritance_planner(PlannerInfo *root) Assert(parse->commandType != CMD_INSERT); + /* Add child target relations. */ + parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); + expand_inherited_rtentry(root, parent_rte, top_parentRTindex); + if (!parent_rte->inh) + { + grouping_planner(root, false, 0.0 /* retrieve all tuples */); + return; + } + /* * We generate a modified instance of the original Query for each target * relation, plan that, and put all the plans into a list that will be @@ -1274,7 +1284,6 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1304,6 +1313,7 @@ inheritance_planner(PlannerInfo *root) RangeTblEntry *child_rte; RelOptInfo *sub_final_rel; Path *subpath; + ListCell *lc1; /* append_rel_list contains all append rels; ignore others */ if (!bms_is_member(appinfo->parent_relid, parent_relids)) @@ -1326,6 +1336,23 @@ inheritance_planner(PlannerInfo *root) memcpy(subroot, parent_root, sizeof(PlannerInfo)); /* + * First off, remove the AppendRelInfos corresponding to child target + * relations, lest query_planner called below sees them as non-target + * children. + */ + subroot->append_rel_list = NIL; + foreach(lc1, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(lc1); + + if (appinfo->parent_relid == parent_parse->resultRelation) + continue; + + subroot->append_rel_list = lappend(subroot->append_rel_list, + appinfo); + } + + /* * Generate modified query with this rel as target. We first apply * adjust_appendrel_attrs, which copies the Query and changes * references to the parent RTE to refer to the current child RTE, @@ -1812,8 +1839,11 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, parse->groupClause = preprocess_groupclause(root, NIL); } - /* Preprocess targetlist */ - tlist = preprocess_targetlist(root); + /* + * Preprocess targetlist. Haven't expanded inheritance yet, so pass + * false. + */ + tlist = preprocess_targetlist(root, false); /* * We are now done hacking up the query's targetlist. Most of the @@ -2175,60 +2205,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, */ if (parse->commandType != CMD_SELECT && !inheritance_update) { - Index rootRelation; - List *withCheckOptionLists; - List *returningLists; - List *rowMarks; - - /* - * If target is a partition root table, we need to mark the - * ModifyTable node appropriately for that. - */ - if (rt_fetch(parse->resultRelation, parse->rtable)->relkind == - RELKIND_PARTITIONED_TABLE) - rootRelation = parse->resultRelation; - else - rootRelation = 0; - - /* - * Set up the WITH CHECK OPTION and RETURNING lists-of-lists, if - * needed. - */ - if (parse->withCheckOptions) - withCheckOptionLists = list_make1(parse->withCheckOptions); - else - withCheckOptionLists = NIL; - - if (parse->returningList) - returningLists = list_make1(parse->returningList); - else - returningLists = NIL; - - /* - * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node - * will have dealt with fetching non-locked marked rows, else we - * need to have ModifyTable do that. - */ - if (parse->rowMarks) - rowMarks = NIL; - else - rowMarks = root->rowMarks; - - path = (Path *) - create_modifytable_path(root, final_rel, - parse->commandType, - parse->canSetTag, - parse->resultRelation, - rootRelation, - false, - list_make1_int(parse->resultRelation), - list_make1(path), - list_make1(root), - withCheckOptionLists, - returningLists, - rowMarks, - parse->onConflict, - assign_special_exec_param(root)); + path = create_single_target_modifytable_path(root, final_rel, + path); } /* And shove it into final_rel */ @@ -2269,6 +2247,66 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, /* Note: currently, we leave it to callers to do set_cheapest() */ } +static Path * +create_single_target_modifytable_path(PlannerInfo *root, + RelOptInfo *final_rel, + Path *subpath) +{ + Query *parse = root->parse; + Index rootRelation; + List *withCheckOptionLists; + List *returningLists; + List *rowMarks; + + /* + * If target is a partition root table, we need to mark the ModifyTable + * node appropriately for that. + */ + if (rt_fetch(parse->resultRelation, parse->rtable)->relkind == + RELKIND_PARTITIONED_TABLE) + rootRelation = parse->resultRelation; + else + rootRelation = 0; + + /* + * Set up the WITH CHECK OPTION and RETURNING lists-of-lists, if needed. + */ + if (parse->withCheckOptions) + withCheckOptionLists = list_make1(parse->withCheckOptions); + else + withCheckOptionLists = NIL; + + if (parse->returningList) + returningLists = list_make1(parse->returningList); + else + returningLists = NIL; + + /* + * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node + * will have dealt with fetching non-locked marked rows, else we + * need to have ModifyTable do that. + */ + if (parse->rowMarks) + rowMarks = NIL; + else + rowMarks = root->rowMarks; + + return (Path *) create_modifytable_path(root, final_rel, + parse->commandType, + parse->canSetTag, + parse->resultRelation, + rootRelation, + false, + list_make1_int(parse->resultRelation), + list_make1(subpath), + list_make1(root), + withCheckOptionLists, + returningLists, + rowMarks, + parse->onConflict, + assign_special_exec_param(root)); +} + /* * Do preprocessing for groupingSets clause and related data. This handles the * preliminary steps of expanding the grouping sets, organizing them into lists @@ -2594,7 +2632,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2619,7 +2657,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } @@ -7022,6 +7060,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -7123,6 +7165,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 0213a37670..154ccda432 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -326,6 +326,12 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) UPPERREL_FINAL, NULL))) add_rtes_to_flat_rtable(rel->subroot, true); } + /* + * A NULL rel also means an unplanned subquery rte, so apply + * flatten_unplanned_rtes. + */ + else + flatten_unplanned_rtes(glob, rte); } rti++; } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 5392d1a561..41a57d16b2 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -65,9 +65,13 @@ static List *expand_targetlist(List *tlist, int command_type, * * As a side effect, if there's an ON CONFLICT UPDATE clause, its targetlist * is also preprocessed (and updated in-place). + * + * inheritance_expanded specifies whether inheritance tables are expanded + * as of calling this function. Caller must specify it because that affects + * which row marking related junk columns get added to the targetlist. */ List * -preprocess_targetlist(PlannerInfo *root) +preprocess_targetlist(PlannerInfo *root, bool inheritance_expanded) { Query *parse = root->parse; int result_relation = parse->resultRelation; @@ -134,6 +138,15 @@ preprocess_targetlist(PlannerInfo *root) if (rc->rti != rc->prti) continue; + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which will change the parent row + * mark's allMarkTypes fields. + */ + if (rc->isParent && !inheritance_expanded) + continue; + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) { /* Need to fetch TID */ diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 55eeb5127c..7bed914f35 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -31,12 +31,15 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" #include "optimizer/cost.h" +#include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" #include "parser/parse_coerce.h" #include "parser/parsetree.h" diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index faba493200..fdc3f53bc0 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -18,31 +18,45 @@ #include "access/table.h" #include "catalog/partition.h" #include "catalog/pg_inherits.h" +#include "catalog/pg_type.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" #include "optimizer/inherit.h" +#include "optimizer/optimizer.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" +#include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" #include "partitioning/partdesc.h" +#include "partitioning/partprune.h" #include "utils/rel.h" -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); +static void expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, + RangeTblEntry *rte, Index rti); static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); + Index parentRTindex, Index rootParentRTindex); +static void add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, Relation parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p, + AppendRelInfo **appinfo_p); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); - +static RelOptInfo *build_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc); +static bool apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *childrel, + RangeTblEntry *childRTE, AppendRelInfo *appinfo); /* * expand_inherited_tables @@ -50,37 +64,150 @@ static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, * into an "append relation". At the conclusion of this process, * the "inh" flag is set in all and only those RTEs that are append * relation parents. + * + * Note that although we're calling the combined output of inheritance set + * relations an "append relation" here, the caller may not always want to + * combine the relations. For example, if the parent of the inheritance + * set is the query's target relation, each child relation is processed + * on its own as the query's target relation. */ void expand_inherited_tables(PlannerInfo *root) { - Index nrtes; + int orig_rtable_size; Index rti; - ListCell *rl; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; /* * expand_inherited_rtentry may add RTEs to parse->rtable. The function is * expected to recursively handle any RTEs that it creates with inh=true. * So just scan as far as the original end of the rtable list. */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) + for (rti = 1; rti < orig_rtable_size; rti++) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); + if (brel == NULL) + continue; + + /* Only consider baserel RTEs that are marked as inherited. */ + if (brel->reloptkind == RELOPT_BASEREL && rte->inh) + expand_inherited_rtentry(root, rte, rti); } } /* * expand_inherited_rtentry - * Check whether a rangetable entry represents an inheritance set. - * If so, add entries for all the child tables to the query's - * rangetable, and build AppendRelInfo nodes for all the child tables - * and add them to root->append_rel_list. If not, clear the entry's - * "inh" flag to prevent later code from looking for AppendRelInfos. + * This initializes RelOptInfos for inheritance child relations if the + * passed-in relation has any + * + * 'rel' is the parent relation, whose range table entry ('rte') has been + * marked to require adding children. Parent could either be a subquery (if + * we flattened UNION ALL query) or a table that's known to have (or once had) + * inheritance children. The latter consists of both regular inheritance + * parents and partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as a child to be scanned as part of the + * inheritance set. The original RTE is considered to represent the whole + * inheritance set. + */ +void +expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + RelOptInfo *rel = find_base_rel(root, rti); + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + RangeTblEntry *childrte; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + childrte = root->simple_rte_array[childRTindex]; + Assert(childrte != NULL); + + /* + * We set the correct value of baserestricinfo and + * baserestrict_min_security below. + */ + root->simple_rel_array[childRTindex] = + build_inheritance_child_rel(root, rel, appinfo->child_relid); + + if (childrte->inh) + expand_inherited_rtentry(root, childrte, childRTindex); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rti); + else + expand_nonpartitioned_inherited_rtentry(root, rte, rti); + } +} + +/* + * expand_nonpartitioned_inherited_rtentry + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. * * Note that the original RTE is considered to represent the whole * inheritance set. The first of the generated RTEs is an RTE for the same @@ -91,272 +218,339 @@ expand_inherited_tables(PlannerInfo *root) * regular inheritance, a parent RTE must always have at least two associated * AppendRelInfos: one corresponding to the parent table as a simple member of * inheritance set and one or more corresponding to the actual children. - * Since a partitioned table is not scanned, it might have only one associated - * AppendRelInfo. */ static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, + RangeTblEntry *rte, + Index rti) { Oid parentOID; - PlanRowMark *oldrc; - Relation oldrelation; - LOCKMODE lockmode; + Relation parentrel; + PlanRowMark *oldrc = NULL; + LOCKMODE lockmode = rte->rellockmode; List *inhOIDs; ListCell *l; + int num_children; + int num_children_added = 0; + RelOptInfo *parent = NULL; - /* Does RT entry allow inheritance? */ - if (!rte->inh) - return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } - /* Fast path for common case of childless table */ + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); parentOID = rte->relid; - if (!has_subclass(parentOID)) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * The rewriter should already have obtained an appropriate lock on each - * relation named in the query. However, for each child relation we add - * to the query, we must obtain an appropriate lock, because this will be - * the first use of those relations in the parse/rewrite/plan pipeline. - * Child rels should use the same lockmode as their parent. - */ - lockmode = rte->rellockmode; /* Scan for all members of inheritance set, acquire needed locks */ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); /* * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite above has_subclass() check, if table - * once had a child but no longer does. + * case. This could happen despite has_subclass() check performed by + * subquery_planner, if table once had a child but no longer does. */ - if (list_length(inhOIDs) < 2) + num_children = list_length(inhOIDs); + if (num_children < 2) { /* Clear flag before returning */ rte->inh = false; return; } - /* - * If parent relation is selected FOR UPDATE/SHARE, we need to mark its - * PlanRowMark as isParent = true, and generate a new PlanRowMark for each - * child. - */ - oldrc = get_plan_rowmark(root->rowMarks, rti); - if (oldrc) - oldrc->isParent = true; + /* Already locked. */ + parentrel = table_open(parentOID, NoLock); /* - * Must open the parent relation to examine its tupdesc. We need not lock - * it; we assume the rewriter already did. + * For inherited update/delete, there's no RelOptInfo for parent and + * we won't be creating one for the children either. */ - oldrelation = table_open(parentOID, NoLock); - - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) + if (rti != root->parse->resultRelation) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); + /* + * If parent relation is selected FOR UPDATE/SHARE, + * preprocess_rowmarks should've set isParent = true. We'll generate + * a new PlanRowMark for each child. + */ + oldrc = get_plan_rowmark(root->rowMarks, rti); + Assert(oldrc == NULL || oldrc->isParent); + + parent = find_base_rel(root, rti); /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. + * Must expand PlannerInfo arrays by num_children before we can add + * children. */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); + Assert(num_children > 0); + expand_planner_arrays(root, num_children); } - else + + foreach(l, inhOIDs) { - List *appinfos = NIL; + Oid childOID = lfirst_oid(l); + /* Already locked. */ + Relation childrel = table_open(childOID, NoLock); RangeTblEntry *childrte; Index childRTindex; + AppendRelInfo *appinfo; /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. */ - foreach(l, inhOIDs) + if (childOID != parentOID && RELATION_IS_OTHER_TEMP(childrel)) { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = table_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - table_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - table_close(newrelation, NoLock); + table_close(childrel, lockmode); + continue; } - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); + add_inheritance_child_rel(root, rte, rti, parentrel, oldrc, + childrel, &childrte, &childRTindex, + &appinfo); + Assert(childrte != NULL && childRTindex > 0 && appinfo != NULL); + /* Close child relations, but keep locks */ + table_close(childrel, NoLock); + num_children_added++; + + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + + if (parent) + { + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + /* Add the RelOptInfo too. */ + (void) build_inheritance_child_rel(root, parent, childRTindex); + } } - table_close(oldrelation, NoLock); + /* + * If all the children except the parent itself in its role as a child + * were temp tables of other backends or were excluded, pretend it's a + * non-inheritance situation; we don't need Append node in that case. The + * duplicate RTE we added for the parent table is harmless, so we don't + * bother to get rid of it; ditto for the useless PlanRowMark node. + */ + if (num_children_added == 1) + rte->inh = false; + + if (parent) + { + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = add_rowmark_junk_columns(root, oldrc); + + build_base_rel_tlists(root, tlist); + } + } + + table_close(parentrel, NoLock); } /* * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. + * Prunes unnecessary partitions of a partitioned table and adds + * remaining ones to the Query and the PlannerInfo + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) + Index parentRTindex, Index rootParentRTindex) { + Relation parentrel; + PartitionDesc partdesc; + LOCKMODE lockmode = parentrte->rellockmode; int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); - check_stack_depth(); + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); + /* Already locked. */ + parentrel = table_open(parentrte->relid, NoLock); + partdesc = RelationGetPartitionDesc(parentrel); /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. + * For inherited update/delete query, just add RTEs and AppendRelInfos + * for *all* partitions. */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); - - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); - - /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. - */ - if (partdesc->nparts == 0) + if (rootParentRTindex == root->parse->resultRelation) { - parentrte->inh = false; - return; + Assert(NULL == get_plan_rowmark(root->rowMarks, rootParentRTindex)); + for (i = 0; i < partdesc->nparts; i++) + { + Oid childOID = partdesc->oids[i]; + /* Already locked. */ + Relation childrel = table_open(childOID, NoLock); + RangeTblEntry *childrte = NULL; + Index childRTindex = 0; + AppendRelInfo *appinfo = NULL; + + /* + * A partitioned child table with 0 children is a dummy rel, so + * don't bother creating any planner objects for it. + */ + if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(childrel)->nparts == 0) + { + table_close(childrel, NoLock); + continue; + } + + add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, NULL, childrel, + &childrte, &childRTindex, &appinfo); + Assert(childrte != NULL && childRTindex > 0 && appinfo != NULL); + table_close(childrel, NoLock); + + if (childrte->inh) + expand_partitioned_rtentry(root, childrte, childRTindex, + rootParentRTindex); + } + + /* Note whether partition key columns are updated. */ + if (!root->partColsUpdated) + root->partColsUpdated = + has_partition_attrs(parentrel, parentrte->updatedCols, NULL); } - - for (i = 0; i < partdesc->nparts; i++) + else { - Oid childOID = partdesc->oids[i]; - Relation childrel; - - /* Open rel; we already have required locks */ - childrel = table_open(childOID, NoLock); + RelOptInfo *parent = find_base_rel(root, parentRTindex); + PlanRowMark *rootrc = NULL; + Bitmapset *partindexes; /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. + * Initialize partitioned_child_rels to contain this RT index. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will + * be bubbled up into root parent's list so that when we've created + * Paths for all the children, the root table's list will contain all + * such indexes. */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); + parent->partitioned_child_rels = list_make1_int(parentRTindex); - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parent); - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); + /* Must expand PlannerInfo arrays before we can add children. */ + if (bms_num_members(partindexes) > 0) + expand_planner_arrays(root, bms_num_members(partindexes)); - /* Close child relation, but keep locks */ - table_close(childrel, NoLock); + /* + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. + */ + parent->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parent->nparts); + + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) + { + Oid childOID = partdesc->oids[i]; + /* Already locked. */ + Relation childrel = table_open(childOID, NoLock); + RangeTblEntry *childrte = NULL; + Index childRTindex = 0; + AppendRelInfo *appinfo = NULL; + + /* + * A partitioned child table with 0 children is a dummy rel, so + * don't bother creating any planner objects for it. + */ + if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(childrel)->nparts == 0) + { + table_close(childrel, NoLock); + continue; + } + + add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, childrel, &childrte, + &childRTindex, &appinfo); + Assert(childrte != NULL && childRTindex > 0 && appinfo != NULL); + table_close(childrel, NoLock); + + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + /* Add the RelOptInfo too. */ + parent->part_rels[i] = build_inheritance_child_rel(root, parent, + childRTindex); + + /* If the child is partitioned itself, expand it too. */ + if (childrte->inh) + { + Assert(parent->part_rels[i]->part_scheme != NULL); + expand_partitioned_rtentry(root, childrte, childRTindex, + rootParentRTindex); + } + } + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *tlist = add_rowmark_junk_columns(root, rootrc); + + build_base_rel_tlists(root, tlist); + } } + + table_close(parentrel, NoLock); } /* - * expand_single_inheritance_child - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) + * The return value is the RelOptInfo that's added. * * PlanRowMarks still carry the top-parent's RTI, and the top-parent's * allMarkTypes field still accumulates values from all descendents. * * "parentrte" and "parentRTindex" are immediate parent's RTE and * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. */ static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, Relation parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p, + AppendRelInfo **appinfo_p) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); Oid childOID = RelationGetRelid(childrel); - RangeTblEntry *childrte; Index childRTindex; + RangeTblEntry *childrte; AppendRelInfo *appinfo; /* * Build an RTE for the child, and attach to query's rangetable list. We * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh = false. Also, set requiredPerms to zero since - * all required permissions checks are done on the original RTE. Likewise, - * set the child's securityQuals to empty, because we only want to apply - * the parent's RLS conditions regardless of what RLS properties + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties * individual children may have. (This is an intentional choice to make * inherited RLS work like regular permissions checks.) The parent * securityQuals will be propagated to children along with other base @@ -366,49 +560,40 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childrte_p = childrte; childrte->relid = childOID; childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) - childrte->inh = true; - else - childrte->inh = false; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); childrte->requiredPerms = 0; childrte->securityQuals = NIL; parse->rtable = lappend(parse->rtable, childrte); childRTindex = list_length(parse->rtable); *childRTindex_p = childRTindex; - /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. - */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) - { - appinfo = make_append_rel_info(parentrel, childrel, - parentRTindex, childRTindex); - *appinfos = lappend(*appinfos, appinfo); + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, childrel, parentRTindex, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); + *appinfo_p = appinfo; - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); } /* @@ -443,6 +628,129 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, } /* + * build_inheritance_child_rel + * Build a RelOptInfo for child relation of an inheritance set + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields based on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turn out to be self-contradictory, + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + * + * It's possible for child rels to have their own children, in which case + * the topmost parent's lateral info must be propagated all the way down. + * That's ensured by having childrel be expanded via this same path. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + */ + if (!apply_child_basequals(root, parent, childrel, childRTE, appinfo) || + relation_excluded_by_constraints(root, childrel, childRTE)) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(childrel); + } + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +add_rowmark_junk_columns(PlannerInfo *root, PlanRowMark *rc) +{ + List *tlist = root->processed_tlist; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + pstrdup(resname), + true); + tlist = lappend(tlist, tle); + + return tlist; +} + +/* * translate_col_privs * Translate a bitmapset representing per-column privileges from the * parent rel's attribute numbering to the child's. @@ -493,3 +801,131 @@ translate_col_privs(const Bitmapset *parent_privs, return child_privs; } + +/* + * apply_child_basequals + * Populate childrel's quals based on rel's quals, translating them using + * appinfo. + * + * If any of the resulting clauses evaluate to false or NULL, we return false + * and don't apply any quals. Caller can mark the relation as a dummy rel in + * this case, since it needn't be scanned. + * + * If any resulting clauses evaluate to true, they're unnecessary and we don't + * apply then. + */ +static bool +apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *childrel, RangeTblEntry *childRTE, + AppendRelInfo *appinfo) +{ + List *childquals; + Index cq_min_security; + ListCell *lc; + + /* + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities for + * const-simplification, and perhaps even pseudoconstant quals. Therefore, + * transform each RestrictInfo separately to see if it reduces to a + * constant or pseudoconstant. (We must process them separately to keep + * track of the security level of each qual.) + */ + childquals = NIL; + cq_min_security = UINT_MAX; + foreach(lc, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, + (Node *) rinfo->clause, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + return false; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + /* track minimum security level among child quals */ + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might have + * securityQuals associated with this particular child node. (Currently + * this can only happen in appendrels originating from UNION ALL; + * inheritance child tables don't have their own securityQuals, see + * expand_inherited_rtentry().) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except that we + * can't make any general deductions from such quals, since they don't + * hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* not likely that we'd see constants here, so no check */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, false, false, + security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* + * OK, we've got all the baserestrictinfo quals for this child. + */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + return true; +} diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 78a96b4ee2..b6d4725a31 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -27,6 +27,7 @@ #include "catalog/catalog.h" #include "catalog/dependency.h" #include "catalog/heap.h" +#include "catalog/partition.h" #include "catalog/pg_am.h" #include "catalog/pg_proc.h" #include "catalog/pg_statistic_ext.h" @@ -107,20 +108,20 @@ static void set_baserel_partition_key_exprs(Relation relation, * important for it. */ void -get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) +get_relation_info(PlannerInfo *root, RangeTblEntry *rte, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; bool hasindex; List *indexinfos = NIL; + bool inhparent = rte->inh; /* * We need not lock the relation since it was already locked, either by * the rewriter or when expand_inherited_rtentry() added it to the query's * rangetable. */ - relation = table_open(relationObjectId, NoLock); + relation = heap_open(rte->relid, NoLock); /* Temporary and unlogged relations are inaccessible during recovery. */ if (!RelationNeedsWAL(relation) && RecoveryInProgress()) @@ -460,7 +461,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * removing an index, or adding a hypothetical index to the indexlist. */ if (get_relation_info_hook) - (*get_relation_info_hook) (root, relationObjectId, inhparent, rel); + (*get_relation_info_hook) (root, rte->relid, rte->inh, rel); } /* @@ -2082,16 +2083,19 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; + + /* + * Since we must've taken a lock on the table, it's okay to simply copy + * the pointers to relcache data here. + */ + rel->boundinfo = partdesc->boundinfo; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 4130514952..631dfae1f6 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -132,6 +132,50 @@ setup_append_rel_array(PlannerInfo *root) } /* + * expand_planner_arrays + * Expand the PlannerInfo arrays by add_size members and initialize the + * the newly added bytes with zero + */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size; + + Assert(add_size > 0); + + new_size = root->simple_rel_array_size + add_size; + + /* Expand various arrays and 0-initialize added bytes. */ + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + + if (root->append_rel_array) + { + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + } + else + { + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + } + + root->simple_rel_array_size = new_size; +} + +/* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. */ @@ -195,6 +239,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->joininfo = NIL; rel->has_eclass_joins = false; rel->consider_partitionwise_join = false; /* might get changed later */ + rel->top_parent_relids = NULL; /* might be changed later */ rel->part_scheme = NULL; rel->nparts = 0; rel->boundinfo = NULL; @@ -217,15 +262,13 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) else rel->top_parent_relids = bms_copy(parent->relids); } - else - rel->top_parent_relids = NULL; /* Check type of rtable entry */ switch (rte->rtekind) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte, rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -273,52 +316,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); - /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. - */ - if (rte->inh) - { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); - - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; - } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); - } - return rel; } diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 8c9721935d..472a6cd331 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -45,6 +45,8 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "partitioning/partprune.h" @@ -433,17 +435,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -537,23 +545,20 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ @@ -561,6 +566,13 @@ prune_append_rel_partitions(RelOptInfo *rel) return NULL; /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. */ @@ -587,15 +599,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index a008ae07da..baa9ba1533 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -15,6 +15,7 @@ #define PATHNODES_H #include "access/sdir.h" +#include "access/tupdesc.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/params.h" diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h index d2418f15cf..fd4c4f7b7e 100644 --- a/src/include/optimizer/inherit.h +++ b/src/include/optimizer/inherit.h @@ -18,5 +18,7 @@ extern void expand_inherited_tables(PlannerInfo *root); +extern void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, + Index rti); #endif /* INHERIT_H */ diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 574bb85b50..3a803b3fd0 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -277,6 +277,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index c337f047cb..04731f532f 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -25,8 +25,8 @@ typedef void (*get_relation_info_hook_type) (PlannerInfo *root, extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; -extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); +extern void get_relation_info(PlannerInfo *root, RangeTblEntry *rte, + RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index a9b2c9026c..27de05ba3e 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -34,7 +34,8 @@ extern Relids get_relids_for_join(Query *query, int joinrelid); /* * prototypes for preptlist.c */ -extern List *preprocess_targetlist(PlannerInfo *root); +extern List *preprocess_targetlist(PlannerInfo *root, + bool inheritance_expanded); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 6bc106831e..1450cef057 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index 30946f77b6..71942394ba 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -2559,16 +2559,16 @@ update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a; Index Cond: (a = 1) -> Nested Loop (actual rows=1 loops=1) -> Append (actual rows=1 loops=1) - -> Bitmap Heap Scan on ab_a1_b1 ab_a1_b1_1 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b1 ab_a1_b1_2 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b2 ab_a1_b2_1 (actual rows=1 loops=1) + -> Bitmap Heap Scan on ab_a1_b2 ab_a1_b2_2 (actual rows=1 loops=1) Recheck Cond: (a = 1) Heap Blocks: exact=1 -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_1 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_2 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) @@ -2580,16 +2580,16 @@ update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a; Index Cond: (a = 1) -> Nested Loop (actual rows=0 loops=1) -> Append (actual rows=1 loops=1) - -> Bitmap Heap Scan on ab_a1_b1 ab_a1_b1_1 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b1 ab_a1_b1_3 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b2 ab_a1_b2_1 (actual rows=1 loops=1) + -> Bitmap Heap Scan on ab_a1_b2 ab_a1_b2_3 (actual rows=1 loops=1) Recheck Cond: (a = 1) Heap Blocks: exact=1 -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_1 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_3 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out index 2e170497c9..a6a499ed4a 100644 --- a/src/test/regress/expected/rowsecurity.out +++ b/src/test/regress/expected/rowsecurity.out @@ -1815,26 +1815,26 @@ AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2; -> Seq Scan on t3 t1_2_2 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Nested Loop - Join Filter: (t1_1_1.b = t1_2.b) + Join Filter: (t1_1_1.b = t1_2_3.b) -> Seq Scan on t2 t1_1_1 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Append - -> Seq Scan on t1 t1_2 + -> Seq Scan on t1 t1_2_3 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t2 t1_2_1 + -> Seq Scan on t2 t1_2_4 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t3 t1_2_2 + -> Seq Scan on t3 t1_2_5 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Nested Loop - Join Filter: (t1_1_2.b = t1_2.b) + Join Filter: (t1_1_2.b = t1_2_6.b) -> Seq Scan on t3 t1_1_2 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Append - -> Seq Scan on t1 t1_2 + -> Seq Scan on t1 t1_2_6 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t2 t1_2_1 + -> Seq Scan on t2 t1_2_7 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t3 t1_2_2 + -> Seq Scan on t3 t1_2_8 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) (37 rows) -- 2.11.0
v23-0002-Teach-planner-to-only-process-unpruned-partition.patch (text/plain, 6.9K) download From df8ace1f78be01f9d08acb6c1f4318b3daf1698a Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 15 Oct 2018 10:59:24 +0900 Subject: [PATCH v23 2/3] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/inherit.c | 1 + src/backend/optimizer/util/relnode.c | 3 +++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/pathnodes.h | 4 ++++ 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 5047b7b1df..cb91ae27d1 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1470,6 +1470,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } if (baserel1) diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index bb1631b68e..ba76184b6e 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -7052,7 +7052,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -7060,9 +7062,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -7143,7 +7143,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -7155,7 +7154,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -7165,9 +7166,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index fdc3f53bc0..d01b4de583 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -444,6 +444,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, /* Perform pruning. */ partindexes = prune_append_rel_partitions(parent); + parentrel->live_parts = partindexes; /* Must expand PlannerInfo arrays before we can add children. */ if (bms_num_members(partindexes) > 0) diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 631dfae1f6..0a0f34ba9e 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1731,6 +1731,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 472a6cd331..babb7d3406 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -438,29 +438,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index baa9ba1533..b5c11aa2c4 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -706,6 +706,10 @@ typedef struct RelOptInfo List *partition_qual; /* partition constraint */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Indexes into part_rels of the non-NULL + * RelOptInfos of unpruned partitions; exists + * to avoid having to iterate over the entire + * part_rels array to filter NULL entries. */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.11.0
v23-0003-Do-not-lock-all-partitions-at-the-beginning.patch (text/plain, 2.8K) download From ada6af7a33e6289243595ed4b54bc55505c6d581 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 12 Oct 2018 19:12:53 +0900 Subject: [PATCH v23 3/3] Do not lock all partitions at the beginning --- src/backend/optimizer/util/inherit.c | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index d01b4de583..1fb36b3e39 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -360,10 +360,6 @@ expand_nonpartitioned_inherited_rtentry(PlannerInfo *root, * * Partitions are added to the query in order in which they are found in * the parent's PartitionDesc. - * - * Note: even though only the unpruned partitions will be added to the - * resulting plan, this still locks *all* partitions via find_all_inheritors - * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, @@ -374,10 +370,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, LOCKMODE lockmode = parentrte->rellockmode; int i; - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - /* Already locked. */ parentrel = table_open(parentrte->relid, NoLock); partdesc = RelationGetPartitionDesc(parentrel); @@ -392,8 +384,8 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, for (i = 0; i < partdesc->nparts; i++) { Oid childOID = partdesc->oids[i]; - /* Already locked. */ - Relation childrel = table_open(childOID, NoLock); + /* Lock the child as it's the first time it's being opened. */ + Relation childrel = table_open(childOID, lockmode); RangeTblEntry *childrte = NULL; Index childRTindex = 0; AppendRelInfo *appinfo = NULL; @@ -444,7 +436,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, /* Perform pruning. */ partindexes = prune_append_rel_partitions(parent); - parentrel->live_parts = partindexes; + parent->live_parts = partindexes; /* Must expand PlannerInfo arrays before we can add children. */ if (bms_num_members(partindexes) > 0) @@ -463,8 +455,8 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, while ((i = bms_next_member(partindexes, i)) >= 0) { Oid childOID = partdesc->oids[i]; - /* Already locked. */ - Relation childrel = table_open(childOID, NoLock); + /* Lock the child as it's the first time it's being opened. */ + Relation childrel = table_open(childOID, lockmode); RangeTblEntry *childrte = NULL; Index childRTindex = 0; AppendRelInfo *appinfo = NULL; -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-22 16:59  Justin Pryzby <pryzby@telsasoft.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  0 siblings, 1 reply; 265+ messages in thread

From: Justin Pryzby @ 2019-02-22 16:59 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; David Rowley <david.rowley@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Amit Langote <amitlangote09@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On Fri, Feb 22, 2019 at 09:45:38PM +0900, Amit Langote wrote:
> I have updated the inheritance expansion patch.
> 
> Patch 0001 rewrites optimizer/utils/inherit.c, so that it allows

Thanks for your continued work on this.

I applied v23 patch and imported one of our customers' schema, and ran explain
on a table with 210 partitions.  With patch applied there are 10x fewer system
calls, as intended.

with patch:
    173 pread64
     76 lseek
     47 open
     38 brk

without patch:
   1276 lseek
    693 pread64
    647 open
    594 brk

> +		if (IS_SIMPLE_REL(rel1) && child_rel1 == NULL)
> +			child_rel1 = build_dummy_partition_rel(root, rel1, baserel1,
> +												   cnt_parts);
> +		if (IS_SIMPLE_REL(rel1) && child_rel2 == NULL)
> +			child_rel2 = build_dummy_partition_rel(root, rel2, baserel2,
> +												   cnt_parts);

Should 2nd "if" say IS_SIMPLE_REL(rel2) ?

Justin




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-22 17:54  Amit Langote <amitlangote09@gmail.com>
  parent: Justin Pryzby <pryzby@telsasoft.com>
  0 siblings, 2 replies; 265+ messages in thread

From: Amit Langote @ 2019-02-22 17:54 UTC (permalink / raw)
  To: Justin Pryzby <pryzby@telsasoft.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Tom Lane <tgl@sss.pgh.pa.us>; David Rowley <david.rowley@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

Hi Justin,

Thanks for checking.

On Sat, Feb 23, 2019 at 1:59 AM Justin Pryzby <pryzby@telsasoft.com> wrote:
>
> On Fri, Feb 22, 2019 at 09:45:38PM +0900, Amit Langote wrote:
> > I have updated the inheritance expansion patch.
> >
> > Patch 0001 rewrites optimizer/utils/inherit.c, so that it allows
>
> Thanks for your continued work on this.
>
> I applied v23 patch and imported one of our customers' schema, and ran explain
> on a table with 210 partitions.  With patch applied there are 10x fewer system
> calls, as intended.
>
> with patch:
>     173 pread64
>      76 lseek
>      47 open
>      38 brk
>
> without patch:
>    1276 lseek
>     693 pread64
>     647 open
>     594 brk

OK, great.  I guess you were running SELECT?  Just in case you missed,
the patch to improve UPDATE/DELETE scalability is no longer part of
this patch series.

> > +             if (IS_SIMPLE_REL(rel1) && child_rel1 == NULL)
> > +                     child_rel1 = build_dummy_partition_rel(root, rel1, baserel1,
> > +                                                                                                cnt_parts);
> > +             if (IS_SIMPLE_REL(rel1) && child_rel2 == NULL)
> > +                     child_rel2 = build_dummy_partition_rel(root, rel2, baserel2,
> > +                                                                                                cnt_parts);
>
> Should 2nd "if" say IS_SIMPLE_REL(rel2) ?

Good catch, fixed.  Apparently not guarded by a test, but I haven't
bothered to add new tests with this patch series.

Please find attached updated patches.  I've made a few updates in last
couple of hours such as improving comments, fixing a few thinkos in
inheritance_planner changes, etc.

Thanks,
Amit

Attachments: download all (3 files, .tar.gz)

v24-0003-Do-not-lock-all-partitions-at-the-beginning.patch (application/octet-stream, 2.5K) download From 526f0ecf8313084c725fbd69bb62b82a4c5d4022 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 12 Oct 2018 19:12:53 +0900 Subject: [PATCH v24 3/3] Do not lock all partitions at the beginning --- src/backend/optimizer/util/inherit.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index d63fb017dc..bb2f281f80 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -362,10 +362,6 @@ expand_regular_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, * * Partitions are added to the query in order in which they are found in * the parent's PartitionDesc. - * - * Note: even though only the unpruned partitions will be added to the - * resulting plan, this still locks *all* partitions via find_all_inheritors - * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, @@ -376,10 +372,6 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, LOCKMODE lockmode = parentrte->rellockmode; int i; - /* If root partitioned table, lock *all* partitions in the tree. */ - if (parentRTindex == rootParentRTindex) - (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - /* Already locked. */ parentrel = table_open(parentrte->relid, NoLock); partdesc = RelationGetPartitionDesc(parentrel); @@ -394,8 +386,8 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, for (i = 0; i < partdesc->nparts; i++) { Oid childOID = partdesc->oids[i]; - /* Already locked. */ - Relation childrel = table_open(childOID, NoLock); + /* Lock the child as it's the first time it's being opened. */ + Relation childrel = table_open(childOID, lockmode); RangeTblEntry *childrte = NULL; Index childRTindex = 0; AppendRelInfo *appinfo = NULL; @@ -472,8 +464,8 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, while ((i = bms_next_member(partindexes, i)) >= 0) { Oid childOID = partdesc->oids[i]; - /* Already locked. */ - Relation childrel = table_open(childOID, NoLock); + /* Lock the child as it's the first time it's being opened. */ + Relation childrel = table_open(childOID, lockmode); RangeTblEntry *childrte = NULL; Index childRTindex = 0; AppendRelInfo *appinfo = NULL; -- 2.17.2 (Apple Git-113)
v24-0002-Teach-planner-to-only-process-unpruned-partition.patch (application/octet-stream, 6.9K) download From 025ad0cf53c81896b80172f12ff080f80f3c855f Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 15 Oct 2018 10:59:24 +0900 Subject: [PATCH v24 2/3] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/inherit.c | 1 + src/backend/optimizer/util/relnode.c | 3 +++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/pathnodes.h | 4 ++++ 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 66a5cf3334..57158d90ec 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1470,6 +1470,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } if (baserel1) diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 6ef8031079..52e6c39fff 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -7027,7 +7027,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -7035,9 +7037,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -7118,7 +7118,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -7130,7 +7129,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -7140,9 +7141,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index c9552405d4..d63fb017dc 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -453,6 +453,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, /* Perform pruning. */ partindexes = prune_append_rel_partitions(parent); + parent->live_parts = partindexes; /* Must expand PlannerInfo arrays before we can add children. */ if (bms_num_members(partindexes) > 0) diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 631dfae1f6..0a0f34ba9e 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1731,6 +1731,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 472a6cd331..babb7d3406 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -438,29 +438,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index a008ae07da..d43be85cf5 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -705,6 +705,10 @@ typedef struct RelOptInfo List *partition_qual; /* partition constraint */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Indexes into part_rels of the non-NULL + * RelOptInfos of unpruned partitions; exists + * to avoid having to iterate over the entire + * part_rels array to filter NULL entries. */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.17.2 (Apple Git-113)
v24-0001-Lazy-creation-of-RTEs-for-inheritance-children.patch (application/octet-stream, 91.3K) download From 5445a4fef5e03425de3c2ca82f76494b9aa8884c Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 26 Oct 2018 16:45:59 +0900 Subject: [PATCH v24 1/3] Lazy creation of RTEs for inheritance children Rewrite inherit.c so that expand_inherited_tables could be invoked from make_one_rel(). That's better because for partitioned tables, we can add only the partitions left after performing partition pruning. Since it's not known what RowMark identities are necessary for individual inheritance children until they're added to the query, preprocess_targetlist must delay adding junk columns if there are any parent PlanRowMarks. For partitioning, although we don't create a RangeTblEntry and RelOptInfo for pruned partitions at make_one_rel time, partitionwise join code relies on the fact that even though partitions may have been pruned, they'd still own a RelOptInfo to handle the outer join case where the pruned partition appears on the nullable side of join. Partitionwise join code deals with that by allocating dummy RelOptInfos for pruned partitions that are based mostly on their parent's properties. There are a few regression test diffs: 1. Caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child, as seen in the partition_aggregate.out test output. 2. Those in postgres_fdw.out are caused by the fact that junk columns required for row marking are added to reltarget->exprs later than user columns, because the row marking junk columns aren't added until the inheritance is expanded which as of this commit is later than it used to be as noted above. 3. RTE aliases changes --- .../postgres_fdw/expected/postgres_fdw.out | 70 +- src/backend/optimizer/path/allpaths.c | 212 +--- src/backend/optimizer/path/joinrels.c | 92 +- src/backend/optimizer/plan/initsplan.c | 49 - src/backend/optimizer/plan/planner.c | 57 +- src/backend/optimizer/plan/setrefs.c | 6 + src/backend/optimizer/prep/preptlist.c | 15 +- src/backend/optimizer/util/inherit.c | 950 +++++++++++++----- src/backend/optimizer/util/plancat.c | 18 +- src/backend/optimizer/util/relnode.c | 95 +- src/backend/partitioning/partprune.c | 44 +- src/include/optimizer/inherit.h | 2 + src/include/optimizer/pathnode.h | 1 + src/include/optimizer/plancat.h | 4 +- src/include/optimizer/prep.h | 3 +- .../regress/expected/partition_aggregate.out | 4 +- src/test/regress/expected/partition_prune.out | 12 +- src/test/regress/expected/rowsecurity.out | 16 +- 18 files changed, 1008 insertions(+), 642 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 42108bd3d4..4d9029911a 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7128,15 +7128,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7166,15 +7166,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7190,8 +7190,8 @@ select * from bar where f1 in (select f1 from foo) for share; -- Check UPDATE with inherited target and an inherited source table explain (verbose, costs off) update bar set f2 = f2 + 100 where f1 in (select f1 from foo); - QUERY PLAN ---------------------------------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------------------------------- Update on public.bar Update on public.bar Foreign Update on public.bar2 @@ -7203,33 +7203,33 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join - Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid + Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo_1.ctid, foo_1.*, foo_1.tableoid Inner Unique: true - Hash Cond: (bar2.f1 = foo.f1) + Hash Cond: (bar2.f1 = foo_1.f1) -> Foreign Scan on public.bar2 Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo_1.f1, foo_1.ctid, foo_1.*, foo_1.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 - Group Key: foo.f1 + Output: foo_1.f1, foo_1.ctid, foo_1.*, foo_1.tableoid + Group Key: foo_1.f1 -> Append - -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 - -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + -> Seq Scan on public.foo foo_1 + Output: foo_1.f1, foo_1.ctid, foo_1.*, foo_1.tableoid + -> Foreign Scan on public.foo2 foo2_1 + Output: foo2_1.f1, foo2_1.ctid, foo2_1.*, foo2_1.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (39 rows) @@ -7276,8 +7276,8 @@ where bar.f1 = ss.f1; -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Merge Join - Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, (ROW(foo.f1)) - Merge Cond: (bar2.f1 = foo.f1) + Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, (ROW(foo_2.f1)) + Merge Cond: (bar2.f1 = foo_2.f1) -> Sort Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Sort Key: bar2.f1 @@ -7285,18 +7285,18 @@ where bar.f1 = ss.f1; Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Sort - Output: (ROW(foo.f1)), foo.f1 - Sort Key: foo.f1 + Output: (ROW(foo_2.f1)), foo_2.f1 + Sort Key: foo_2.f1 -> Append - -> Seq Scan on public.foo - Output: ROW(foo.f1), foo.f1 - -> Foreign Scan on public.foo2 - Output: ROW(foo2.f1), foo2.f1 + -> Seq Scan on public.foo foo_2 + Output: ROW(foo_2.f1), foo_2.f1 + -> Foreign Scan on public.foo2 foo2_2 + Output: ROW(foo2_2.f1), foo2_2.f1 Remote SQL: SELECT f1 FROM public.loct1 - -> Seq Scan on public.foo foo_1 - Output: ROW((foo_1.f1 + 3)), (foo_1.f1 + 3) - -> Foreign Scan on public.foo2 foo2_1 - Output: ROW((foo2_1.f1 + 3)), (foo2_1.f1 + 3) + -> Seq Scan on public.foo foo_3 + Output: ROW((foo_3.f1 + 3)), (foo_3.f1 + 3) + -> Foreign Scan on public.foo2 foo2_3 + Output: ROW((foo2_3.f1 + 3)), (foo2_3.f1 + 3) Remote SQL: SELECT f1 FROM public.loct1 (45 rows) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 0debac75c6..f132abe57d 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -44,7 +44,6 @@ #include "optimizer/tlist.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -138,9 +137,6 @@ static void subquery_push_qual(Query *subquery, static void recurse_push_qual(Node *setOp, Query *topquery, RangeTblEntry *rte, Index rti, Node *qual); static void remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel); -static bool apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, - RelOptInfo *childrel, - RangeTblEntry *childRTE, AppendRelInfo *appinfo); /* @@ -155,6 +151,16 @@ make_one_rel(PlannerInfo *root, List *joinlist) Index rti; double total_pages; + /* + * Expand RT entries that represent inherited or partitioned tables. + * This will perform partition pruning on partitioned tables in the + * original range table and also recursively on any child partitioned + * tables that were added by the expansion of the original parent(s). + * As new entries are added to the range table, various arrays in the + * PlannerInfo will be expanded accordingly. + */ + expand_inherited_tables(root); + /* * Construct the all_baserels Relids set. */ @@ -945,40 +951,12 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); Assert(IS_SIMPLE_REL(rel)); - /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, we will bubble up - * the indexes of partitioned relations that appear down in the tree, so - * that when we've created Paths for all the children, the root - * partitioned table's list will contain all such indexes. - */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - rel->partitioned_child_rels = list_make1_int(rti); - - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - /* * If this is a partitioned baserel, set the consider_partitionwise_join * flag; currently, we only consider partitionwise joins with the baserel @@ -1025,50 +1003,26 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * expand_inherited_tables(). */ childrel = find_base_rel(root, childRTindex); + Assert(childrel != NULL); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - /* - * 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 - * baserestrictinfo quals. + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals or quals that contradict its + * constraints. */ - if (!apply_child_basequals(root, rel, childrel, childRTE, appinfo)) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); + if (IS_DUMMY_REL(childrel)) continue; - } - - if (relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* - * This child need not be scanned, so we can omit it from the - * appendrel. - */ - set_dummy_rel_pathlist(childrel); - continue; - } /* - * CE failed, so finish copying/modifying targetlist and join quals. + * Copy/Modify targetlist. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -3557,134 +3511,6 @@ generate_partitionwise_join_paths(PlannerInfo *root, RelOptInfo *rel) list_free(live_children); } -/* - * apply_child_basequals - * Populate childrel's quals based on rel's quals, translating them using - * appinfo. - * - * If any of the resulting clauses evaluate to false or NULL, we return false - * and don't apply any quals. Caller can mark the relation as a dummy rel in - * this case, since it needn't be scanned. - * - * If any resulting clauses evaluate to true, they're unnecessary and we don't - * apply then. - */ -static bool -apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, - RelOptInfo *childrel, RangeTblEntry *childRTE, - AppendRelInfo *appinfo) -{ - List *childquals; - Index cq_min_security; - ListCell *lc; - - /* - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities for - * const-simplification, and perhaps even pseudoconstant quals. Therefore, - * transform each RestrictInfo separately to see if it reduces to a - * constant or pseudoconstant. (We must process them separately to keep - * track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - return false; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. (Currently - * this can only happen in appendrels originating from UNION ALL; - * inheritance child tables don't have their own securityQuals, see - * expand_inherited_rtentry().) Pull any such securityQuals up into the - * baserestrictinfo for the child. This is similar to - * process_security_barrier_quals() for the parent rel, except that we - * can't make any general deductions from such quals, since they don't - * hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - return true; -} - /***************************************************************************** * DEBUG SUPPORT *****************************************************************************/ diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index dfbbfdac6d..66a5cf3334 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -14,6 +14,7 @@ */ #include "postgres.h" +#include "access/table.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" @@ -21,6 +22,8 @@ #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" +#include "optimizer/tlist.h" +#include "parser/parsetree.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -52,6 +55,9 @@ static SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, Relids left_relids, Relids right_relids); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, Relation parentrel, + int partidx); /* @@ -1318,6 +1324,8 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, RelOptInfo *joinrel, SpecialJoinInfo *parent_sjinfo, List *parent_restrictlist) { + Relation baserel1 = NULL, + baserel2 = NULL; bool rel1_is_simple = IS_SIMPLE_REL(rel1); bool rel2_is_simple = IS_SIMPLE_REL(rel2); int nparts; @@ -1368,6 +1376,18 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, nparts = joinrel->nparts; + if (rel1_is_simple) + { + RangeTblEntry *rte = planner_rt_fetch(rel1->relid, root); + + baserel1 = table_open(rte->relid, NoLock); + } + if (rel2_is_simple) + { + RangeTblEntry *rte = planner_rt_fetch(rel2->relid, root); + + baserel2 = table_open(rte->relid, NoLock); + } /* * Create child-join relations for this partitioned join, if those don't * exist. Add paths to child-joins for a pair of child relations @@ -1384,6 +1404,13 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (rel1_is_simple && child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, baserel1, + cnt_parts); + if (rel2_is_simple && child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, baserel2, + cnt_parts); + /* * If a child table has consider_partitionwise_join=false, it means * that it's a dummy relation for which we skipped setting up tlist @@ -1444,6 +1471,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, child_joinrel, child_sjinfo, child_restrictlist); } + + if (baserel1) + table_close(baserel1, NoLock); + if (baserel2) + table_close(baserel2, NoLock); } /* @@ -1462,8 +1494,14 @@ update_child_rel_info(PlannerInfo *root, (Node *) rel->reltarget->exprs, 1, &appinfo); - /* Make child entries in the EquivalenceClass as well */ - if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) + /* + * Make child entries in the EquivalenceClass as well. If the childrel + * appears to be a dummy one (one built by build_dummy_partition_rel()), + * no need to make any new entries, because anything that'd need those + * can instead use the parent's (rel). + */ + if (childrel->relid != rel->relid && + (rel->has_eclass_joins || has_useful_pathkeys(root, rel))) add_child_rel_equivalences(root, appinfo, rel, childrel); childrel->has_eclass_joins = rel->has_eclass_joins; } @@ -1674,3 +1712,53 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +static RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, + Relation parentrel, int partidx) +{ + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + AppendRelInfo *appinfo = make_append_rel_info(parentrel, parentrel, + parent->relid, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 2afc3f1dfe..5e8a8b1eee 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -419,7 +419,6 @@ void create_lateral_join_info(PlannerInfo *root) { bool found_laterals = false; - Relids prev_parents PG_USED_FOR_ASSERTS_ONLY = NULL; Index rti; ListCell *lc; @@ -618,54 +617,6 @@ create_lateral_join_info(PlannerInfo *root) bms_add_member(brel2->lateral_referencers, rti); } } - - /* - * Lastly, propagate lateral_relids and lateral_referencers from appendrel - * parent rels to their child rels. We intentionally give each child rel - * the same minimum parameterization, even though it's quite possible that - * some don't reference all the lateral rels. This is because any append - * path for the parent will have to have the same parameterization for - * every child anyway, and there's no value in forcing extra - * reparameterize_path() calls. Similarly, a lateral reference to the - * parent prevents use of otherwise-movable join rels for each child. - * - * It's possible for child rels to have their own children, in which case - * the topmost parent's lateral info must be propagated all the way down. - * This code handles that case correctly so long as append_rel_list has - * entries for child relationships before grandchild relationships, which - * is an okay assumption right now, but we'll need to be careful to - * preserve it. The assertions below check for incorrect ordering. - */ - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); - RelOptInfo *parentrel = root->simple_rel_array[appinfo->parent_relid]; - RelOptInfo *childrel = root->simple_rel_array[appinfo->child_relid]; - - /* - * If we're processing a subquery of a query with inherited target rel - * (cf. inheritance_planner), append_rel_list may contain entries for - * tables that are not part of the current subquery and hence have no - * RelOptInfo. Ignore them. We can ignore dead rels, too. - */ - if (parentrel == NULL || !IS_SIMPLE_REL(parentrel)) - continue; - - /* Verify that children are processed before grandchildren */ -#ifdef USE_ASSERT_CHECKING - prev_parents = bms_add_member(prev_parents, appinfo->parent_relid); - Assert(!bms_is_member(appinfo->child_relid, prev_parents)); -#endif - - /* OK, propagate info down */ - Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - Assert(childrel->direct_lateral_relids == NULL); - childrel->direct_lateral_relids = parentrel->direct_lateral_relids; - Assert(childrel->lateral_relids == NULL); - childrel->lateral_relids = parentrel->lateral_relids; - Assert(childrel->lateral_referencers == NULL); - childrel->lateral_referencers = parentrel->lateral_referencers; - } } diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 5579dfa65e..6ef8031079 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -25,6 +25,7 @@ #include "access/table.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -714,26 +715,23 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } /* * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). + * pullup (so that all non-inherited RTEs are present). */ preprocess_rowmarks(root); - /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain RTE_RELATION entries, so it's OK (and marginally more efficient) - * to do it after checking for joins and other special RTEs. We must do - * this after pulling up subqueries, else we'd fail to handle inherited - * tables in subqueries. - */ - expand_inherited_tables(root); - /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to @@ -1210,9 +1208,19 @@ inheritance_planner(PlannerInfo *root) Query *parent_parse; Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); PlannerInfo **parent_roots = NULL; + List *orig_append_rel_list = list_copy(root->append_rel_list); Assert(parse->commandType != CMD_INSERT); + /* Add child target relations. */ + parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); + expand_inherited_rtentry(root, parent_rte, top_parentRTindex); + if (!parent_rte->inh) + { + grouping_planner(root, false, 0.0 /* retrieve all tuples */); + return; + } + /* * We generate a modified instance of the original Query for each target * relation, plan that, and put all the plans into a list that will be @@ -1274,7 +1282,6 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1325,6 +1332,9 @@ inheritance_planner(PlannerInfo *root) subroot = makeNode(PlannerInfo); memcpy(subroot, parent_root, sizeof(PlannerInfo)); + /* grouping_planner doesn't need to see the target children. */ + subroot->append_rel_list = list_copy(orig_append_rel_list); + /* * Generate modified query with this rel as target. We first apply * adjust_appendrel_attrs, which copies the Query and changes @@ -1812,8 +1822,11 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, parse->groupClause = preprocess_groupclause(root, NIL); } - /* Preprocess targetlist */ - tlist = preprocess_targetlist(root); + /* + * Preprocess targetlist. Haven't expanded inheritance yet, so pass + * false. + */ + tlist = preprocess_targetlist(root, false); /* * We are now done hacking up the query's targetlist. Most of the @@ -2594,7 +2607,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2619,7 +2632,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } @@ -7022,6 +7035,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -7123,6 +7140,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 0213a37670..154ccda432 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -326,6 +326,12 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) UPPERREL_FINAL, NULL))) add_rtes_to_flat_rtable(rel->subroot, true); } + /* + * A NULL rel also means an unplanned subquery rte, so apply + * flatten_unplanned_rtes. + */ + else + flatten_unplanned_rtes(glob, rte); } rti++; } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 5392d1a561..41a57d16b2 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -65,9 +65,13 @@ static List *expand_targetlist(List *tlist, int command_type, * * As a side effect, if there's an ON CONFLICT UPDATE clause, its targetlist * is also preprocessed (and updated in-place). + * + * inheritance_expanded specifies whether inheritance tables are expanded + * as of calling this function. Caller must specify it because that affects + * which row marking related junk columns get added to the targetlist. */ List * -preprocess_targetlist(PlannerInfo *root) +preprocess_targetlist(PlannerInfo *root, bool inheritance_expanded) { Query *parse = root->parse; int result_relation = parse->resultRelation; @@ -134,6 +138,15 @@ preprocess_targetlist(PlannerInfo *root) if (rc->rti != rc->prti) continue; + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which will change the parent row + * mark's allMarkTypes fields. + */ + if (rc->isParent && !inheritance_expanded) + continue; + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) { /* Need to fetch TID */ diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index faba493200..c9552405d4 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -18,31 +18,45 @@ #include "access/table.h" #include "catalog/partition.h" #include "catalog/pg_inherits.h" +#include "catalog/pg_type.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" #include "optimizer/inherit.h" +#include "optimizer/optimizer.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" +#include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" #include "partitioning/partdesc.h" +#include "partitioning/partprune.h" #include "utils/rel.h" -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); +static void expand_regular_inherited_rtentry(PlannerInfo *root, + RangeTblEntry *rte, Index rti); static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); + Index parentRTindex, Index rootParentRTindex); +static void add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, Relation parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p, + AppendRelInfo **appinfo_p); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); - +static RelOptInfo *build_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex); +static List *get_rowmark_junk_tles(PlannerInfo *root, PlanRowMark *rc); +static bool apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *childrel, + RangeTblEntry *childRTE, AppendRelInfo *appinfo); /* * expand_inherited_tables @@ -50,37 +64,146 @@ static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, * into an "append relation". At the conclusion of this process, * the "inh" flag is set in all and only those RTEs that are append * relation parents. + * + * Note that although we're calling the combined output of inheritance set + * relations an "append relation" here, the caller may not always want to + * combine the relations. For example, if the parent of the inheritance + * set is the query's target relation, each child relation is processed + * on its own as the query's target relation. */ void expand_inherited_tables(PlannerInfo *root) { - Index nrtes; + int orig_rtable_size; Index rti; - ListCell *rl; + + Assert(root->simple_rel_array_size > 0); + orig_rtable_size = root->simple_rel_array_size; /* * expand_inherited_rtentry may add RTEs to parse->rtable. The function is - * expected to recursively handle any RTEs that it creates with inh=true. - * So just scan as far as the original end of the rtable list. + * expected to recursively handle any RTEs (existing or newly created) with + * inh=true. So just scan as far as the original end of the rtable list. */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) + for (rti = 1; rti < orig_rtable_size; rti++) { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); + RelOptInfo *brel = root->simple_rel_array[rti]; + RangeTblEntry *rte = root->simple_rte_array[rti]; - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); + /* There may be empty slots corresponding to non-baserel RTEs */ + if (brel == NULL) + continue; + + /* Only consider baserel RTEs that are marked as inherited. */ + if (brel->reloptkind == RELOPT_BASEREL && rte->inh) + expand_inherited_rtentry(root, rte, rti); } } /* * expand_inherited_rtentry - * Check whether a rangetable entry represents an inheritance set. - * If so, add entries for all the child tables to the query's - * rangetable, and build AppendRelInfo nodes for all the child tables - * and add them to root->append_rel_list. If not, clear the entry's - * "inh" flag to prevent later code from looking for AppendRelInfos. + * This initializes RelOptInfos for inheritance child relations if the + * passed-in relation has any + * + * Passed in relation could either be a subquery (if a UNION ALL subquery was + * flattened) or a table that's known to have (or once had) inheritance + * children. The latter consists of both regular inheritance parents and + * partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as a child to be scanned as part of the + * inheritance set. The original RTE is considered to represent the whole + * inheritance set. + */ +void +expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +{ + Assert(rte->inh); + /* Inheritance parent (partitioned or not) or UNION ALL parent subquery. */ + Assert(rte->rtekind == RTE_RELATION || rte->rtekind == RTE_SUBQUERY); + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + * + * It might be a bit odd that this code is in this, because there is + * nothing to expand really. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + ListCell *l; + RelOptInfo *rel = find_base_rel(root, rti); + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + RangeTblEntry *childrte; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + childrte = root->simple_rte_array[childRTindex]; + Assert(childrte != NULL); + root->simple_rel_array[childRTindex] = + build_inheritance_child_rel(root, rel, childRTindex); + + /* Child may itself be an inherited relation. */ + if (childrte->inh) + expand_inherited_rtentry(root, childrte, childRTindex); + } + } + else + { + Assert(rte->rtekind == RTE_RELATION); + Assert(has_subclass(rte->relid)); + + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query. However, for each child relation + * we add to the query, we must obtain an appropriate lock, because + * this will be the first use of those relations in the + * parse/rewrite/plan pipeline. Child rels should use the same + * lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti, rti); + else + expand_regular_inherited_rtentry(root, rte, rti); + } +} + +/* + * expand_regular_inherited_rtentry + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. * * Note that the original RTE is considered to represent the whole * inheritance set. The first of the generated RTEs is an RTE for the same @@ -91,272 +214,352 @@ expand_inherited_tables(PlannerInfo *root) * regular inheritance, a parent RTE must always have at least two associated * AppendRelInfos: one corresponding to the parent table as a simple member of * inheritance set and one or more corresponding to the actual children. - * Since a partitioned table is not scanned, it might have only one associated - * AppendRelInfo. */ static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +expand_regular_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, + Index rti) { Oid parentOID; - PlanRowMark *oldrc; - Relation oldrelation; - LOCKMODE lockmode; + Relation parentrel; + PlanRowMark *oldrc = NULL; + LOCKMODE lockmode = rte->rellockmode; List *inhOIDs; ListCell *l; + int num_children; + int num_children_added = 0; + RelOptInfo *parent = NULL; - /* Does RT entry allow inheritance? */ - if (!rte->inh) - return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } - /* Fast path for common case of childless table */ + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); parentOID = rte->relid; - if (!has_subclass(parentOID)) - { - /* Clear flag before returning */ - rte->inh = false; - return; - } - - /* - * The rewriter should already have obtained an appropriate lock on each - * relation named in the query. However, for each child relation we add - * to the query, we must obtain an appropriate lock, because this will be - * the first use of those relations in the parse/rewrite/plan pipeline. - * Child rels should use the same lockmode as their parent. - */ - lockmode = rte->rellockmode; /* Scan for all members of inheritance set, acquire needed locks */ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); /* * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite above has_subclass() check, if table - * once had a child but no longer does. + * case. This could happen despite has_subclass() check performed by + * subquery_planner, if table once had a child but no longer does. */ - if (list_length(inhOIDs) < 2) + num_children = list_length(inhOIDs); + if (num_children < 2) { /* Clear flag before returning */ rte->inh = false; return; } - /* - * If parent relation is selected FOR UPDATE/SHARE, we need to mark its - * PlanRowMark as isParent = true, and generate a new PlanRowMark for each - * child. - */ - oldrc = get_plan_rowmark(root->rowMarks, rti); - if (oldrc) - oldrc->isParent = true; + /* Already locked. */ + parentrel = table_open(parentOID, NoLock); /* - * Must open the parent relation to examine its tupdesc. We need not lock - * it; we assume the rewriter already did. + * For inherited update/delete, there's no RelOptInfo for parent and we + * won't be creating one for the children either, so the following info + * is unnecessary. */ - oldrelation = table_open(parentOID, NoLock); - - /* Scan the inheritance set and expand it */ - if (RelationGetPartitionDesc(oldrelation) != NULL) + if (rti != root->parse->resultRelation) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); + /* + * If parent relation is selected FOR UPDATE/SHARE, + * preprocess_rowmarks should've set isParent = true. We'll generate + * a new PlanRowMark for each child. + */ + oldrc = get_plan_rowmark(root->rowMarks, rti); + Assert(oldrc == NULL || oldrc->isParent); + + parent = find_base_rel(root, rti); /* - * If this table has partitions, recursively expand them in the order - * in which they appear in the PartitionDesc. While at it, also - * extract the partition key columns of all the partitioned tables. + * Must expand PlannerInfo arrays by num_children before we can add + * children. */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); + Assert(num_children > 0); + expand_planner_arrays(root, num_children); } - else + + foreach(l, inhOIDs) { - List *appinfos = NIL; + Oid childOID = lfirst_oid(l); + /* Already locked. */ + Relation childrel = table_open(childOID, NoLock); RangeTblEntry *childrte; Index childRTindex; + AppendRelInfo *appinfo; /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. */ - foreach(l, inhOIDs) + if (childOID != parentOID && RELATION_IS_OTHER_TEMP(childrel)) { - Oid childOID = lfirst_oid(l); - Relation newrelation; - - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = table_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - table_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - table_close(newrelation, NoLock); + table_close(childrel, lockmode); + continue; } - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); + /* Add RTE, AppendRelInfo, PlanRowMark for the child. */ + add_inheritance_child_rel(root, rte, rti, parentrel, oldrc, + childrel, &childrte, &childRTindex, + &appinfo); + Assert(childrte != NULL && childRTindex > 0 && appinfo != NULL); + /* Close child relations, but keep locks */ + table_close(childrel, NoLock); + num_children_added++; + + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + + /* Also create the RelOptInfo in the non-target inheritance case. */ + if (parent) + { + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + /* Add the RelOptInfo too. */ + (void) build_inheritance_child_rel(root, parent, childRTindex); + } } - table_close(oldrelation, NoLock); + /* + * If all the children except the parent itself in its role as a child + * were temp tables of other backends or were excluded, pretend it's a + * non-inheritance situation; we don't need Append node in that case. The + * duplicate RTE we added for the parent table is harmless, so we don't + * bother to get rid of it; ditto for the useless PlanRowMark node. + */ + if (num_children_added == 1) + rte->inh = false; + + if (parent) + { + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *junk_tlist = get_rowmark_junk_tles(root, oldrc); + + build_base_rel_tlists(root, junk_tlist); + root->processed_tlist = list_concat(root->processed_tlist, + junk_tlist); + } + } + + table_close(parentrel, NoLock); } /* * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. + * Add partitions to Query and PlannerInfo + * + * If we're expanding source inheritance, there's enought information to + * perform partition pruning, so only add those that survive pruning. + * + * Partitions are added to the query in order in which they are found in + * the parent's PartitionDesc. + * + * Note: even though only the unpruned partitions will be added to the + * resulting plan, this still locks *all* partitions via find_all_inheritors + * when this function is called for the root partitioned table. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) + Index parentRTindex, Index rootParentRTindex) { + Relation parentrel; + PartitionDesc partdesc; + LOCKMODE lockmode = parentrte->rellockmode; int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); - check_stack_depth(); + /* If root partitioned table, lock *all* partitions in the tree. */ + if (parentRTindex == rootParentRTindex) + (void) find_all_inheritors(parentrte->relid, lockmode, NULL); - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); - - Assert(parentrte->inh); + /* Already locked. */ + parentrel = table_open(parentrte->relid, NoLock); + partdesc = RelationGetPartitionDesc(parentrel); /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. + * For inherited update/delete query, just add RTEs and AppendRelInfos + * for *all* partitions. */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); - - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); - - /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. - */ - if (partdesc->nparts == 0) + if (rootParentRTindex == root->parse->resultRelation) { - parentrte->inh = false; - return; - } + Assert(NULL == get_plan_rowmark(root->rowMarks, rootParentRTindex)); + for (i = 0; i < partdesc->nparts; i++) + { + Oid childOID = partdesc->oids[i]; + /* Already locked. */ + Relation childrel = table_open(childOID, NoLock); + RangeTblEntry *childrte = NULL; + Index childRTindex = 0; + AppendRelInfo *appinfo = NULL; - for (i = 0; i < partdesc->nparts; i++) - { - Oid childOID = partdesc->oids[i]; - Relation childrel; + /* + * A partitioned child table with 0 children is a dummy rel, so + * don't bother creating any planner objects for it. + */ + if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(childrel)->nparts == 0) + { + table_close(childrel, NoLock); + continue; + } - /* Open rel; we already have required locks */ - childrel = table_open(childOID, NoLock); + /* Adds RTE and AppendRelInfo for the child. */ + add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, NULL, childrel, + &childrte, &childRTindex, &appinfo); + Assert(childrte != NULL && childRTindex > 0 && appinfo != NULL); + table_close(childrel, NoLock); + + /* Handle sub-partitioned child recursively. */ + if (childrte->inh) + expand_partitioned_rtentry(root, childrte, childRTindex, + rootParentRTindex); + } /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. + * Note whether partition key columns are updated; due to recursion, + * also considers sub-partitioned children's partition key being + * updated. */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); - - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); - - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); - - /* Close child relation, but keep locks */ - table_close(childrel, NoLock); + if (!root->partColsUpdated) + root->partColsUpdated = + has_partition_attrs(parentrel, parentrte->updatedCols, NULL); } + else + { + /* Source inheritance case. */ + RelOptInfo *parent = find_base_rel(root, parentRTindex); + PlanRowMark *rootrc = NULL; + Bitmapset *partindexes; + + /* + * Add the parent to partitioned_child_rels. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will + * be bubbled up into root parent's list so that when we've created + * Paths for all the children, the root table's list will contain all + * such indexes. + */ + parent->partitioned_child_rels = list_make1_int(parentRTindex); + + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parent); + + /* Must expand PlannerInfo arrays before we can add children. */ + if (bms_num_members(partindexes) > 0) + expand_planner_arrays(root, bms_num_members(partindexes)); + + /* + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. + */ + parent->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parent->nparts); + + rootrc = get_plan_rowmark(root->rowMarks, rootParentRTindex); + Assert(rootrc == NULL || rootrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) + { + Oid childOID = partdesc->oids[i]; + /* Already locked. */ + Relation childrel = table_open(childOID, NoLock); + RangeTblEntry *childrte = NULL; + Index childRTindex = 0; + AppendRelInfo *appinfo = NULL; + + /* + * A partitioned child table with 0 children is a dummy rel, so + * don't bother creating any planner objects for it. + */ + if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(childrel)->nparts == 0) + { + table_close(childrel, NoLock); + continue; + } + + add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, rootrc, childrel, &childrte, + &childRTindex, &appinfo); + Assert(childrte != NULL && childRTindex > 0 && appinfo != NULL); + table_close(childrel, NoLock); + + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + /* Add the RelOptInfo too. */ + parent->part_rels[i] = build_inheritance_child_rel(root, parent, + childRTindex); + + /* If the child is partitioned itself, expand it too. */ + if (childrte->inh) + { + Assert(parent->part_rels[i]->part_scheme != NULL); + expand_partitioned_rtentry(root, childrte, childRTindex, + rootParentRTindex); + } + } + + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (rootrc) + { + List *junk_tlist = get_rowmark_junk_tles(root, rootrc); + + build_base_rel_tlists(root, junk_tlist); + root->processed_tlist = list_concat(root->processed_tlist, + junk_tlist); + } + } + + table_close(parentrel, NoLock); } /* - * expand_single_inheritance_child - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. - * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. * * PlanRowMarks still carry the top-parent's RTI, and the top-parent's * allMarkTypes field still accumulates values from all descendents. * - * "parentrte" and "parentRTindex" are immediate parent's RTE and - * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. + * "parentrte" and "parentRTindex" are immediate parent's RTE and RTI, resp. + * "top_parentrc" is top parent's PlanRowMark. */ static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, Relation parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p, + AppendRelInfo **appinfo_p) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); Oid childOID = RelationGetRelid(childrel); - RangeTblEntry *childrte; Index childRTindex; + RangeTblEntry *childrte; AppendRelInfo *appinfo; /* * Build an RTE for the child, and attach to query's rangetable list. We * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh = false. Also, set requiredPerms to zero since - * all required permissions checks are done on the original RTE. Likewise, - * set the child's securityQuals to empty, because we only want to apply - * the parent's RLS conditions regardless of what RLS properties + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties * individual children may have. (This is an intentional choice to make * inherited RLS work like regular permissions checks.) The parent * securityQuals will be propagated to children along with other base @@ -366,49 +569,40 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childrte_p = childrte; childrte->relid = childOID; childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) - childrte->inh = true; - else - childrte->inh = false; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); childrte->requiredPerms = 0; childrte->securityQuals = NIL; parse->rtable = lappend(parse->rtable, childrte); childRTindex = list_length(parse->rtable); *childRTindex_p = childRTindex; - /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. - */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) - { - appinfo = make_append_rel_info(parentrel, childrel, - parentRTindex, childRTindex); - *appinfos = lappend(*appinfos, appinfo); + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, childrel, parentRTindex, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); + *appinfo_p = appinfo; - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); } /* @@ -442,6 +636,136 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, } } +/* + * build_inheritance_child_rel + * Build a RelOptInfo for child relation of an inheritance set + * + * After creating the RelOptInfo for the given child RT index, it goes on to + * initialize some of its fields based on the parent RelOptInfo. + * + * If the quals in baserestrictinfo turn out to be self-contradictory, + * RelOptInfo is marked dummy before returning. + */ +static RelOptInfo * +build_inheritance_child_rel(PlannerInfo *root, + RelOptInfo *parent, + Index childRTindex) +{ + RelOptInfo *childrel; + RangeTblEntry *childRTE = root->simple_rte_array[childRTindex]; + AppendRelInfo *appinfo = root->append_rel_array[childRTindex]; + + /* Build the RelOptInfo. */ + childrel = build_simple_rel(root, childRTindex, parent); + + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child rel + * the same minimum parameterization, even though it's quite possible that + * some don't reference all the lateral rels. This is because any append + * path for the parent will have to have the same parameterization for + * every child anyway, and there's no value in forcing extra + * reparameterize_path() calls. Similarly, a lateral reference to the + * parent prevents use of otherwise-movable join rels for each child. + * + * It's possible for child rels to have their own children, in which case + * the topmost parent's lateral info must be propagated all the way down. + * That's ensured by having childrel be expanded via this same path. + */ + childrel->direct_lateral_relids = parent->direct_lateral_relids; + childrel->lateral_relids = parent->lateral_relids; + childrel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + */ + if (!apply_child_basequals(root, parent, childrel, childRTE, appinfo)) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. Note that + * apply_child_basequals won't have copied quals in this case. + */ + set_dummy_rel_pathlist(childrel); + } + + /* Constraint exclusion. */ + if (relation_excluded_by_constraints(root, childrel, childRTE)) + set_dummy_rel_pathlist(childrel); + + return childrel; +} + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark in + * plannodes.h. + */ +static List * +get_rowmark_junk_tles(PlannerInfo *root, PlanRowMark *rc) +{ + int tlist_len = list_length(root->processed_tlist); + List *junk_tles = NIL; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + tlist_len + 1, + pstrdup(resname), + true); + junk_tles = lappend(junk_tles, tle); + tlist_len++; + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(root->simple_rte_array[rc->rti], + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + tlist_len + 1, + pstrdup(resname), + true); + junk_tles = lappend(junk_tles, tle); + tlist_len++; + } + + /* For inheritance cases, always fetch the tableoid too. */ + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + tlist_len + 1, + pstrdup(resname), + true); + junk_tles = lappend(junk_tles, tle); + + return junk_tles; +} + /* * translate_col_privs * Translate a bitmapset representing per-column privileges from the @@ -493,3 +817,131 @@ translate_col_privs(const Bitmapset *parent_privs, return child_privs; } + +/* + * apply_child_basequals + * Populate childrel's quals based on rel's quals, translating them using + * appinfo. + * + * If any of the resulting clauses evaluate to false or NULL, we return false + * and don't apply any quals. Caller can mark the relation as a dummy rel in + * this case, since it needn't be scanned. + * + * If any resulting clauses evaluate to true, they're unnecessary and we don't + * apply then. + */ +static bool +apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *childrel, RangeTblEntry *childRTE, + AppendRelInfo *appinfo) +{ + List *childquals; + Index cq_min_security; + ListCell *lc; + + /* + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities for + * const-simplification, and perhaps even pseudoconstant quals. Therefore, + * transform each RestrictInfo separately to see if it reduces to a + * constant or pseudoconstant. (We must process them separately to keep + * track of the security level of each qual.) + */ + childquals = NIL; + cq_min_security = UINT_MAX; + foreach(lc, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, + (Node *) rinfo->clause, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + return false; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + /* track minimum security level among child quals */ + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might have + * securityQuals associated with this particular child node. (Currently + * this can only happen in appendrels originating from UNION ALL; + * inheritance child tables don't have their own securityQuals, see + * expand_inherited_rtentry().) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except that we + * can't make any general deductions from such quals, since they don't + * hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* not likely that we'd see constants here, so no check */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, false, false, + security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* + * OK, we've got all the baserestrictinfo quals for this child. + */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + return true; +} diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 78a96b4ee2..b6d4725a31 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -27,6 +27,7 @@ #include "catalog/catalog.h" #include "catalog/dependency.h" #include "catalog/heap.h" +#include "catalog/partition.h" #include "catalog/pg_am.h" #include "catalog/pg_proc.h" #include "catalog/pg_statistic_ext.h" @@ -107,20 +108,20 @@ static void set_baserel_partition_key_exprs(Relation relation, * important for it. */ void -get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) +get_relation_info(PlannerInfo *root, RangeTblEntry *rte, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; bool hasindex; List *indexinfos = NIL; + bool inhparent = rte->inh; /* * We need not lock the relation since it was already locked, either by * the rewriter or when expand_inherited_rtentry() added it to the query's * rangetable. */ - relation = table_open(relationObjectId, NoLock); + relation = heap_open(rte->relid, NoLock); /* Temporary and unlogged relations are inaccessible during recovery. */ if (!RelationNeedsWAL(relation) && RecoveryInProgress()) @@ -460,7 +461,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * removing an index, or adding a hypothetical index to the indexlist. */ if (get_relation_info_hook) - (*get_relation_info_hook) (root, relationObjectId, inhparent, rel); + (*get_relation_info_hook) (root, rte->relid, rte->inh, rel); } /* @@ -2082,16 +2083,19 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; + + /* + * Since we must've taken a lock on the table, it's okay to simply copy + * the pointers to relcache data here. + */ + rel->boundinfo = partdesc->boundinfo; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 4130514952..631dfae1f6 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -131,6 +131,50 @@ setup_append_rel_array(PlannerInfo *root) } } +/* + * expand_planner_arrays + * Expand the PlannerInfo arrays by add_size members and initialize the + * the newly added bytes with zero + */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size; + + Assert(add_size > 0); + + new_size = root->simple_rel_array_size + add_size; + + /* Expand various arrays and 0-initialize added bytes. */ + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + + if (root->append_rel_array) + { + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + } + else + { + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + } + + root->simple_rel_array_size = new_size; +} + /* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. @@ -195,6 +239,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->joininfo = NIL; rel->has_eclass_joins = false; rel->consider_partitionwise_join = false; /* might get changed later */ + rel->top_parent_relids = NULL; /* might be changed later */ rel->part_scheme = NULL; rel->nparts = 0; rel->boundinfo = NULL; @@ -217,15 +262,13 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) else rel->top_parent_relids = bms_copy(parent->relids); } - else - rel->top_parent_relids = NULL; /* Check type of rtable entry */ switch (rte->rtekind) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte, rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -273,52 +316,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); - /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. - */ - if (rte->inh) - { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); - - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; - } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); - } - return rel; } diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 8c9721935d..472a6cd331 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -45,6 +45,8 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "partitioning/partprune.h" @@ -433,17 +435,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -537,29 +545,33 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ if (rel->nparts == 0) return NULL; + /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. @@ -587,15 +599,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h index d2418f15cf..fd4c4f7b7e 100644 --- a/src/include/optimizer/inherit.h +++ b/src/include/optimizer/inherit.h @@ -18,5 +18,7 @@ extern void expand_inherited_tables(PlannerInfo *root); +extern void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, + Index rti); #endif /* INHERIT_H */ diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 574bb85b50..3a803b3fd0 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -277,6 +277,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index c337f047cb..04731f532f 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -25,8 +25,8 @@ typedef void (*get_relation_info_hook_type) (PlannerInfo *root, extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; -extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); +extern void get_relation_info(PlannerInfo *root, RangeTblEntry *rte, + RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index a9b2c9026c..27de05ba3e 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -34,7 +34,8 @@ extern Relids get_relids_for_join(Query *query, int joinrelid); /* * prototypes for preptlist.c */ -extern List *preprocess_targetlist(PlannerInfo *root); +extern List *preprocess_targetlist(PlannerInfo *root, + bool inheritance_expanded); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 6bc106831e..1450cef057 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index 30946f77b6..71942394ba 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -2559,16 +2559,16 @@ update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a; Index Cond: (a = 1) -> Nested Loop (actual rows=1 loops=1) -> Append (actual rows=1 loops=1) - -> Bitmap Heap Scan on ab_a1_b1 ab_a1_b1_1 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b1 ab_a1_b1_2 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b2 ab_a1_b2_1 (actual rows=1 loops=1) + -> Bitmap Heap Scan on ab_a1_b2 ab_a1_b2_2 (actual rows=1 loops=1) Recheck Cond: (a = 1) Heap Blocks: exact=1 -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_1 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_2 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) @@ -2580,16 +2580,16 @@ update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a; Index Cond: (a = 1) -> Nested Loop (actual rows=0 loops=1) -> Append (actual rows=1 loops=1) - -> Bitmap Heap Scan on ab_a1_b1 ab_a1_b1_1 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b1 ab_a1_b1_3 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b2 ab_a1_b2_1 (actual rows=1 loops=1) + -> Bitmap Heap Scan on ab_a1_b2 ab_a1_b2_3 (actual rows=1 loops=1) Recheck Cond: (a = 1) Heap Blocks: exact=1 -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_1 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_3 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out index 2e170497c9..a6a499ed4a 100644 --- a/src/test/regress/expected/rowsecurity.out +++ b/src/test/regress/expected/rowsecurity.out @@ -1815,26 +1815,26 @@ AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2; -> Seq Scan on t3 t1_2_2 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Nested Loop - Join Filter: (t1_1_1.b = t1_2.b) + Join Filter: (t1_1_1.b = t1_2_3.b) -> Seq Scan on t2 t1_1_1 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Append - -> Seq Scan on t1 t1_2 + -> Seq Scan on t1 t1_2_3 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t2 t1_2_1 + -> Seq Scan on t2 t1_2_4 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t3 t1_2_2 + -> Seq Scan on t3 t1_2_5 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Nested Loop - Join Filter: (t1_1_2.b = t1_2.b) + Join Filter: (t1_1_2.b = t1_2_6.b) -> Seq Scan on t3 t1_1_2 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Append - -> Seq Scan on t1 t1_2 + -> Seq Scan on t1 t1_2_6 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t2 t1_2_1 + -> Seq Scan on t2 t1_2_7 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t3 t1_2_2 + -> Seq Scan on t3 t1_2_8 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) (37 rows) -- 2.17.2 (Apple Git-113)
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-02-22 18:01  Justin Pryzby <pryzby@telsasoft.com>
  parent: Amit Langote <amitlangote09@gmail.com>
  1 sibling, 0 replies; 265+ messages in thread

From: Justin Pryzby @ 2019-02-22 18:01 UTC (permalink / raw)
  To: Amit Langote <amitlangote09@gmail.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Tom Lane <tgl@sss.pgh.pa.us>; David Rowley <david.rowley@2ndquadrant.com>; Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; pgsql-hackers

On Sat, Feb 23, 2019 at 02:54:35AM +0900, Amit Langote wrote:
> > On Fri, Feb 22, 2019 at 09:45:38PM +0900, Amit Langote wrote:
> > > I have updated the inheritance expansion patch.
> > >
> > > Patch 0001 rewrites optimizer/utils/inherit.c, so that it allows
> >
> > Thanks for your continued work on this.
> >
> > I applied v23 patch and imported one of our customers' schema, and ran explain
> > on a table with 210 partitions.  With patch applied there are 10x fewer system
> > calls, as intended.
> 
> OK, great.  I guess you were running SELECT?  Just in case you missed,
> the patch to improve UPDATE/DELETE scalability is no longer part of
> this patch series.

Yes, I understand the scope is reduced.

Actually, when I tested last month, I don't think I realized that UPDATE/DELETE
was included.  Otherwise I would've also tested to see if it resolves the
excessive RAM use with many partitions, with the explanation given that query
is being replanned for every partition.

I set target version to v12.
https://commitfest.postgresql.org/22/1778/

Justin




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-02-25 06:24  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Amit Langote <amitlangote09@gmail.com>
  1 sibling, 1 reply; 265+ messages in thread

From: Imai, Yoshikazu @ 2019-02-25 06:24 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Justin Pryzby <pryzby@telsasoft.com>; pgsql-hackers

Hi Amit-san.

On Fri, Feb 22, 2019 at 5:55 PM, Amit Langote wrote:
> 
> Please find attached updated patches.  I've made a few updates in last
> couple of hours such as improving comments, fixing a few thinkos in
> inheritance_planner changes, etc.

Thanks for the patch. 

While doing code review of v24-0001, I found the performance degradation case.

[creating tables]
drop table rt;
create table rt (a int, b int, c int) partition by range (a);
\o /dev/null
select 'create table rt' || x::text || ' partition of rt for values from (' ||
 (x)::text || ') to (' || (x+1)::text || ');' from generate_series(1, 3) x;
\gexec
\o

drop table if exists jrt;
create table jrt (a int, b int, c int) partition by range (a);
\o /dev/null
select 'create table jrt' || x::text || ' partition of jrt for values from (' ||
 (x)::text || ') to (' || (x+1)::text || ');' from generate_series(1, 1024) x;
\gexec
\o

[update_pt_with_joining_another_pt.sql]
update rt set c = jrt.c + 100 from jrt where rt.b = jrt.b;

[pgbench]
pgbench -n -f update_pt_with_joining_another_pt_for_ptkey.sql -T 60 postgres

[results]
(part_num_rt, part_num_jrt)  master  patched(0001)
---------------------------  ------  -------------
                  (3, 1024)    8.06           5.89
                  (3, 2048)    1.52           0.87
                  (6, 1024)    4.11           1.77



With HEAD, we add target inheritance and source inheritance to parse->rtable in inheritance_planner and copy and adjust it for child tables at beginning of each planning of child tables.

With the 0001 patch, we add target inheritance to parse->rtable in inheritance_planner and add source inheritance to parse->rtable in make_one_rel(under grouping_planner()) during each planning of child tables.
Adding source inheritance to parse->rtable may be the same process between each planning of child tables and it might be useless. OTOH, from the POV of making inheritance-expansion-at-bottom better, expanding source inheritance in make_one_rel seems correct design to me.

How should we do that...?

--
Yoshikazu Imai



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-03-01 13:01  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  0 siblings, 3 replies; 265+ messages in thread

From: Amit Langote @ 2019-03-01 13:01 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: 'Amit Langote' <amitlangote09@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Justin Pryzby <pryzby@telsasoft.com>; pgsql-hackers

Imai-san,

Thanks for testing and sorry it took me a while to reply.

On 2019/02/25 15:24, Imai, Yoshikazu wrote:
> [update_pt_with_joining_another_pt.sql]
> update rt set c = jrt.c + 100 from jrt where rt.b = jrt.b;
> 
> [pgbench]
> pgbench -n -f update_pt_with_joining_another_pt_for_ptkey.sql -T 60 postgres
> 
> [results]
> (part_num_rt, part_num_jrt)  master  patched(0001)
> ---------------------------  ------  -------------
>                   (3, 1024)    8.06           5.89
>                   (3, 2048)    1.52           0.87
>                   (6, 1024)    4.11           1.77
> 
> 
> 
> With HEAD, we add target inheritance and source inheritance to parse->rtable in inheritance_planner and copy and adjust it for child tables at beginning of each planning of child tables.
> 
> With the 0001 patch, we add target inheritance to parse->rtable in inheritance_planner and add source inheritance to parse->rtable in make_one_rel(under grouping_planner()) during each planning of child tables.
> Adding source inheritance to parse->rtable may be the same process between each planning of child tables and it might be useless. OTOH, from the POV of making inheritance-expansion-at-bottom better, expanding source inheritance in make_one_rel seems correct design to me.
> 
> How should we do that...?

To solve this problem, I ended up teaching inheritance_planner to reuse
the objects for *source* inheritance child relations (RTEs,
AppendRelInfos, and PlanRowMarks) created during the planning of the 1st
child query for the planning of subsequent child queries.  Set of source
child relations don't change between different planning runs, so it's okay
to do so.  Of course, I had to make sure that query_planner (which is not
in the charge of adding source inheritance child objects) can notice that.

Please find attached updated patches.  Will update source code comments,
commit message and perform other fine-tuning over the weekend if possible.

Thanks,
Amit

Attachments: download all (2 files, .tar.gz)

v25-0001-Lazy-creation-of-RTEs-for-inheritance-children.patch (text/plain, 97.2K) download From 2f4c085145c3991c8d7160eab0c90d5d3f9d713e Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Fri, 26 Oct 2018 16:45:59 +0900 Subject: [PATCH v25 1/2] Lazy creation of RTEs for inheritance children Rewrite inherit.c so that source inheritance could be expanded during query_planner(). That's better because for partitioned tables, we can add only the partitions left after performing partition pruning. Since it's not known what RowMark identities are necessary for individual inheritance children until they're added to the query, preprocess_targetlist must delay adding junk columns if there are any parent PlanRowMarks. For partitioning, although we don't create a RangeTblEntry and RelOptInfo for pruned partitions at make_one_rel time, partitionwise join code relies on the fact that even though partitions may have been pruned, they'd still own a RelOptInfo to handle the outer join case where the pruned partition appears on the nullable side of join. Partitionwise join code deals with that by allocating dummy RelOptInfos for pruned partitions that are based mostly on their parent's properties. There are a few regression test diffs: 1. Caused by the fact that we no longer allocate a duplicate RT entry for a partitioned table in its role as child, as seen in the partition_aggregate.out test output. 2. Those in postgres_fdw.out are caused by the fact that junk columns required for row marking are added to reltarget->exprs later than user columns, because the row marking junk columns aren't added until the inheritance is expanded which as of this commit is later than it used to be as noted above. --- contrib/postgres_fdw/expected/postgres_fdw.out | 24 +- src/backend/optimizer/path/allpaths.c | 202 +------ src/backend/optimizer/path/joinrels.c | 92 +++- src/backend/optimizer/plan/initsplan.c | 53 +- src/backend/optimizer/plan/planmain.c | 15 +- src/backend/optimizer/plan/planner.c | 271 +++++++--- src/backend/optimizer/plan/setrefs.c | 6 + src/backend/optimizer/prep/preptlist.c | 130 +++-- src/backend/optimizer/util/inherit.c | 620 +++++++++++++--------- src/backend/optimizer/util/plancat.c | 18 +- src/backend/optimizer/util/relnode.c | 359 +++++++++++-- src/backend/partitioning/partprune.c | 44 +- src/include/nodes/pathnodes.h | 7 + src/include/optimizer/inherit.h | 5 +- src/include/optimizer/pathnode.h | 3 + src/include/optimizer/plancat.h | 4 +- src/include/optimizer/planmain.h | 1 + src/include/optimizer/prep.h | 2 + src/test/regress/expected/partition_aggregate.out | 4 +- 19 files changed, 1185 insertions(+), 675 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 42108bd3d4..4d31cfed5d 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7128,15 +7128,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7166,15 +7166,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7203,15 +7203,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 0debac75c6..29ac5d8b0e 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -44,7 +44,6 @@ #include "optimizer/tlist.h" #include "parser/parse_clause.h" #include "parser/parsetree.h" -#include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -138,9 +137,6 @@ static void subquery_push_qual(Query *subquery, static void recurse_push_qual(Node *setOp, Query *topquery, RangeTblEntry *rte, Index rti, Node *qual); static void remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel); -static bool apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, - RelOptInfo *childrel, - RangeTblEntry *childRTE, AppendRelInfo *appinfo); /* @@ -945,8 +941,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -954,32 +948,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, Assert(IS_SIMPLE_REL(rel)); /* - * Initialize partitioned_child_rels to contain this RT index. - * - * Note that during the set_append_rel_pathlist() phase, we will bubble up - * the indexes of partitioned relations that appear down in the tree, so - * that when we've created Paths for all the children, the root - * partitioned table's list will contain all such indexes. - */ - if (rte->relkind == RELKIND_PARTITIONED_TABLE) - rel->partitioned_child_rels = list_make1_int(rti); - - /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - - /* * If this is a partitioned baserel, set the consider_partitionwise_join * flag; currently, we only consider partitionwise joins with the baserel * if its targetlist doesn't contain a whole-row Var. @@ -1025,50 +993,26 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childRTindex = appinfo->child_relid; childRTE = root->simple_rte_array[childRTindex]; + Assert(childRTE != NULL); /* - * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * The child rel's RelOptInfo was created during + * add_other_rels_to_query(). */ childrel = find_base_rel(root, childRTindex); + Assert(childrel != NULL); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); - continue; - } - /* - * 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 - * baserestrictinfo quals. + * Child relation may have been marked dummy if build_append_child_rel + * found self-contradictory quals or quals that contradict its + * constraints. */ - if (!apply_child_basequals(root, rel, childrel, childRTE, appinfo)) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); + if (IS_DUMMY_REL(childrel)) continue; - } - - if (relation_excluded_by_constraints(root, childrel, childRTE)) - { - /* - * This child need not be scanned, so we can omit it from the - * appendrel. - */ - set_dummy_rel_pathlist(childrel); - continue; - } /* - * CE failed, so finish copying/modifying targetlist and join quals. + * Copy/Modify targetlist. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -3557,134 +3501,6 @@ generate_partitionwise_join_paths(PlannerInfo *root, RelOptInfo *rel) list_free(live_children); } -/* - * apply_child_basequals - * Populate childrel's quals based on rel's quals, translating them using - * appinfo. - * - * If any of the resulting clauses evaluate to false or NULL, we return false - * and don't apply any quals. Caller can mark the relation as a dummy rel in - * this case, since it needn't be scanned. - * - * If any resulting clauses evaluate to true, they're unnecessary and we don't - * apply then. - */ -static bool -apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, - RelOptInfo *childrel, RangeTblEntry *childRTE, - AppendRelInfo *appinfo) -{ - List *childquals; - Index cq_min_security; - ListCell *lc; - - /* - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities for - * const-simplification, and perhaps even pseudoconstant quals. Therefore, - * transform each RestrictInfo separately to see if it reduces to a - * constant or pseudoconstant. (We must process them separately to keep - * track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - return false; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. (Currently - * this can only happen in appendrels originating from UNION ALL; - * inheritance child tables don't have their own securityQuals, see - * expand_inherited_rtentry().) Pull any such securityQuals up into the - * baserestrictinfo for the child. This is similar to - * process_security_barrier_quals() for the parent rel, except that we - * can't make any general deductions from such quals, since they don't - * hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - return true; -} - /***************************************************************************** * DEBUG SUPPORT *****************************************************************************/ diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index dfbbfdac6d..66a5cf3334 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -14,6 +14,7 @@ */ #include "postgres.h" +#include "access/table.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" @@ -21,6 +22,8 @@ #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" +#include "optimizer/tlist.h" +#include "parser/parsetree.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -52,6 +55,9 @@ static SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, Relids left_relids, Relids right_relids); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, Relation parentrel, + int partidx); /* @@ -1318,6 +1324,8 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, RelOptInfo *joinrel, SpecialJoinInfo *parent_sjinfo, List *parent_restrictlist) { + Relation baserel1 = NULL, + baserel2 = NULL; bool rel1_is_simple = IS_SIMPLE_REL(rel1); bool rel2_is_simple = IS_SIMPLE_REL(rel2); int nparts; @@ -1368,6 +1376,18 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, nparts = joinrel->nparts; + if (rel1_is_simple) + { + RangeTblEntry *rte = planner_rt_fetch(rel1->relid, root); + + baserel1 = table_open(rte->relid, NoLock); + } + if (rel2_is_simple) + { + RangeTblEntry *rte = planner_rt_fetch(rel2->relid, root); + + baserel2 = table_open(rte->relid, NoLock); + } /* * Create child-join relations for this partitioned join, if those don't * exist. Add paths to child-joins for a pair of child relations @@ -1384,6 +1404,13 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (rel1_is_simple && child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, baserel1, + cnt_parts); + if (rel2_is_simple && child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, baserel2, + cnt_parts); + /* * If a child table has consider_partitionwise_join=false, it means * that it's a dummy relation for which we skipped setting up tlist @@ -1444,6 +1471,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, child_joinrel, child_sjinfo, child_restrictlist); } + + if (baserel1) + table_close(baserel1, NoLock); + if (baserel2) + table_close(baserel2, NoLock); } /* @@ -1462,8 +1494,14 @@ update_child_rel_info(PlannerInfo *root, (Node *) rel->reltarget->exprs, 1, &appinfo); - /* Make child entries in the EquivalenceClass as well */ - if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) + /* + * Make child entries in the EquivalenceClass as well. If the childrel + * appears to be a dummy one (one built by build_dummy_partition_rel()), + * no need to make any new entries, because anything that'd need those + * can instead use the parent's (rel). + */ + if (childrel->relid != rel->relid && + (rel->has_eclass_joins || has_useful_pathkeys(root, rel))) add_child_rel_equivalences(root, appinfo, rel, childrel); childrel->has_eclass_joins = rel->has_eclass_joins; } @@ -1674,3 +1712,53 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +static RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, + Relation parentrel, int partidx) +{ + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + AppendRelInfo *appinfo = make_append_rel_info(parentrel, parentrel, + parent->relid, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 2afc3f1dfe..54fc4dda97 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -20,6 +20,7 @@ #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" +#include "optimizer/inherit.h" #include "optimizer/joininfo.h" #include "optimizer/optimizer.h" #include "optimizer/pathnode.h" @@ -30,6 +31,7 @@ #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" #include "parser/analyze.h" +#include "parser/parsetree.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -97,10 +99,11 @@ static void check_hashjoinable(RestrictInfo *restrictinfo); * jtnode. Internally, the function recurses through the jointree. * * At the end of this process, there should be one baserel RelOptInfo for - * every non-join RTE that is used in the query. Therefore, this routine - * is the only place that should call build_simple_rel with reloptkind - * RELOPT_BASEREL. (Note: build_simple_rel recurses internally to build - * "other rel" RelOptInfos for the members of any appendrels we find here.) + * every non-join RTE that is specified in the query. Therefore, this + * routine is the only place that should call build_simple_rel with + * reloptkind RELOPT_BASEREL. (Note: "other rel" RelOptInfos for the + * members of any appendrels we find here are added built later when + * query_planner calls add_other_rels_to_query().) */ void add_base_rels_to_query(PlannerInfo *root, Node *jtnode) @@ -133,6 +136,48 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode) (int) nodeTag(jtnode)); } +/* + * add_other_rels_to_query + * + * Scan the query's jointree and for each base rels that is an appendrel, + * create otherrel RelOptInfos of its children + * + * At the end of this process, there should be RelOptInfos for all relations + * that will be scanned by the query. + */ +void +add_other_rels_to_query(PlannerInfo *root, Node *jtnode) +{ + if (jtnode == NULL) + return; + if (IsA(jtnode, RangeTblRef)) + { + int varno = ((RangeTblRef *) jtnode)->rtindex; + RangeTblEntry *rte = rt_fetch(varno, root->parse->rtable); + + if (rte->inh) + (void) add_appendrel_other_rels(root, rte, varno); + } + else if (IsA(jtnode, FromExpr)) + { + FromExpr *f = (FromExpr *) jtnode; + ListCell *l; + + foreach(l, f->fromlist) + add_other_rels_to_query(root, lfirst(l)); + } + else if (IsA(jtnode, JoinExpr)) + { + JoinExpr *j = (JoinExpr *) jtnode; + + add_other_rels_to_query(root, j->larg); + add_other_rels_to_query(root, j->rarg); + } + else + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(jtnode)); +} + /***************************************************************************** * diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 3cedd01c98..03c81772a3 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -159,10 +159,8 @@ query_planner(PlannerInfo *root, List *tlist, setup_append_rel_array(root); /* - * Construct RelOptInfo nodes for all base relations in query, and - * indirectly for all appendrel member relations ("other rels"). This - * will give us a RelOptInfo for every "simple" (non-join) rel involved in - * the query. + * Construct RelOptInfo nodes for all base relations directly mentioned + * in query, but not any appendrel member relations ("other rels") yet. * * Note: the reason we find the rels by searching the jointree and * appendrel list, rather than just scanning the rangetable, is that the @@ -204,6 +202,15 @@ query_planner(PlannerInfo *root, List *tlist, generate_base_implied_equalities(root); /* + * Now that we have restrict clauses figured out and assigned to proper + * base rels, we can proceed to add otherrels, that is, UNION ALL child + * tables, inheritance child tables. Having restrict clauses ready helps + * to exclude any children that wouldn't be necessary to scan, based on + * constraint exclusion and partition pruning. + */ + add_other_rels_to_query(root, (Node *) root->parse->jointree); + + /* * We have completed merging equivalence sets, so it's now possible to * generate pathkeys in canonical form; so compute query_pathkeys and * other pathkeys fields in PlannerInfo. diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index bc81535905..ddc4d27f67 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -25,6 +25,7 @@ #include "access/table.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -641,6 +642,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->wt_param_id = -1; root->non_recursive_path = NULL; root->partColsUpdated = false; + root->contains_inherit_children = false; /* * If there is a WITH list, process each WITH query and either convert it @@ -714,27 +716,24 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } /* * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). + * pullup (so that all non-inherited RTEs are present). */ preprocess_rowmarks(root); /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain RTE_RELATION entries, so it's OK (and marginally more efficient) - * to do it after checking for joins and other special RTEs. We must do - * this after pulling up subqueries, else we'd fail to handle inherited - * tables in subqueries. - */ - expand_inherited_tables(root); - - /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -1207,13 +1206,44 @@ inheritance_planner(PlannerInfo *root) Index rti; RangeTblEntry *parent_rte; PlannerInfo *parent_root; - Query *parent_parse; Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); PlannerInfo **parent_roots = NULL; + List *orig_append_rel_list = list_copy(root->append_rel_list); + List *orig_row_marks = list_copy(root->rowMarks); + List *orig_rtable = list_copy(root->parse->rtable); + List *rtable_with_target; + List *source_appinfos = NIL; + List *source_child_rowmarks = NIL; + List *source_child_rtes = NIL; Assert(parse->commandType != CMD_INSERT); /* + * Add child target relations. Note that this only adds the children of + * the query's target relation and no other. Especially, children of any + * source relations are added when the loop below calls grouping_planner + * on the *1st* child target relation. + */ + expand_inherited_target_rtentry(root); + parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); + + /* + * If parent no longer has any children, then treat this as an update of + * a single table. + */ + if (!parent_rte->inh) + { + grouping_planner(root, false, 0.0 /* retrieve all tuples */); + return; + } + + /* + * This one also contains the child target relations, but no other + * child relations. + */ + rtable_with_target = list_copy(root->parse->rtable); + + /* * We generate a modified instance of the original Query for each target * relation, plan that, and put all the plans into a list that will be * controlled by a single ModifyTable node. All the instances share the @@ -1229,10 +1259,13 @@ inheritance_planner(PlannerInfo *root) * management of the rowMarks list. * * To begin with, generate a bitmapset of the relids of the subquery RTEs. + * We use orig_rtable, not rtable_with_target (parse->rtable), because we + * wouldn't need to consider any newly added RTEs as they all must be + * RTE_RELATION entries. */ subqueryRTindexes = NULL; rti = 1; - foreach(lc, parse->rtable) + foreach(lc, orig_rtable) { RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); @@ -1245,26 +1278,24 @@ inheritance_planner(PlannerInfo *root) * Next, we want to identify which AppendRelInfo items contain references * to any of the aforesaid subquery RTEs. These items will need to be * copied and modified to adjust their subquery references; whereas the - * other ones need not be touched. It's worth being tense over this - * because we can usually avoid processing most of the AppendRelInfo - * items, thereby saving O(N^2) space and time when the target is a large - * inheritance tree. We can identify AppendRelInfo items by their - * child_relid, since that should be unique within the list. + * other ones need not be touched. We can assume that all (if any) + * entries in orig_append_rel_list contain references to subquery RTEs, + * because they correspond to flattneed UNION ALL subqueries. Especially, + * we don't need to bother with those added when adding the child target + * relations. We can identify AppendRelInfo items by their child_relid, + * since that should be unique within the list. */ modifiableARIindexes = NULL; - if (subqueryRTindexes != NULL) + foreach(lc, orig_append_rel_list) { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); + AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) || - bms_is_member(appinfo->child_relid, subqueryRTindexes) || - bms_overlap(pull_varnos((Node *) appinfo->translated_vars), - subqueryRTindexes)) - modifiableARIindexes = bms_add_member(modifiableARIindexes, - appinfo->child_relid); - } + Assert(bms_is_member(appinfo->parent_relid, subqueryRTindexes) || + bms_is_member(appinfo->child_relid, subqueryRTindexes) || + bms_overlap(pull_varnos((Node *) appinfo->translated_vars), + subqueryRTindexes)); + modifiableARIindexes = bms_add_member(modifiableARIindexes, + appinfo->child_relid); } /* @@ -1274,7 +1305,6 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1316,7 +1346,6 @@ inheritance_planner(PlannerInfo *root) */ parent_root = parent_roots[appinfo->parent_relid]; Assert(parent_root != NULL); - parent_parse = parent_root->parse; /* * We need a working copy of the PlannerInfo so that we can control @@ -1326,6 +1355,52 @@ inheritance_planner(PlannerInfo *root) memcpy(subroot, parent_root, sizeof(PlannerInfo)); /* + * Per the above comment, we'll be changing only the AppendRelInfos + * that are contained in orig_append_rel_list, so only copy those. + */ + subroot->append_rel_list = copyObject(orig_append_rel_list); + + /* + * Likewise, for PlanRowMarks. (Fortunately, the executor doesn't + * need to see the modified copies --- we can just pass it the + * original rowMarks list.) + */ + subroot->rowMarks = copyObject(orig_row_marks); + + /* + * No need to copy of the RTEs themselves, but do copy the List + * structure. + */ + subroot->parse->rtable = list_copy(rtable_with_target); + + /* + * If this isn't the first child query, then we can use the child + * objects for source child relations created during the planning of + * 1st child query. IOW, this planning run doesn't need to create the + * child objects again, indicated by setting contains_inherit_children + * sub-PlannerInfo. + */ + if (source_appinfos) + { + subroot->append_rel_list = list_concat(subroot->append_rel_list, + source_appinfos); + /* + * XXX Assert(source_child_rowmarks != NIL); + */ + subroot->rowMarks = list_concat(subroot->rowMarks, + source_child_rowmarks); + Assert(source_child_rtes != NIL); + subroot->parse->rtable = list_concat(subroot->parse->rtable, + source_child_rtes); + + /* + * We have the children, so no need to add them again during this + * planning cycle. + */ + subroot->contains_inherit_children = true; + } + + /* * Generate modified query with this rel as target. We first apply * adjust_appendrel_attrs, which copies the Query and changes * references to the parent RTE to refer to the current child RTE, @@ -1333,7 +1408,7 @@ inheritance_planner(PlannerInfo *root) */ subroot->parse = (Query *) adjust_appendrel_attrs(parent_root, - (Node *) parent_parse, + (Node *) subroot->parse, 1, &appinfo); /* @@ -1399,41 +1474,6 @@ inheritance_planner(PlannerInfo *root) nominalRelation = appinfo->child_relid; /* - * The rowMarks list might contain references to subquery RTEs, so - * make a copy that we can apply ChangeVarNodes to. (Fortunately, the - * executor doesn't need to see the modified copies --- we can just - * pass it the original rowMarks list.) - */ - subroot->rowMarks = copyObject(parent_root->rowMarks); - - /* - * The append_rel_list likewise might contain references to subquery - * RTEs (if any subqueries were flattenable UNION ALLs). So prepare - * to apply ChangeVarNodes to that, too. As explained above, we only - * want to copy items that actually contain such references; the rest - * can just get linked into the subroot's append_rel_list. - * - * If we know there are no such references, we can just use the outer - * append_rel_list unmodified. - */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - subroot->append_rel_list = NIL; - foreach(lc2, parent_root->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, modifiableARIindexes)) - appinfo2 = copyObject(appinfo2); - - subroot->append_rel_list = lappend(subroot->append_rel_list, - appinfo2); - } - } - - /* * Add placeholders to the child Query's rangetable list to fill the * RT indexes already reserved for subqueries in previous children. * These won't be referenced, so there's no need to make them very @@ -1446,23 +1486,23 @@ inheritance_planner(PlannerInfo *root) /* * If this isn't the first child Query, generate duplicates of all * subquery RTEs, and adjust Var numbering to reference the - * duplicates. To simplify the loop logic, we scan the original rtable - * not the copy just made by adjust_appendrel_attrs; that should be OK - * since subquery RTEs couldn't contain any references to the target - * rel. + * duplicates. Note that we scan the original rtable before any + * child target relations were added, which is OK, because no other + * RTEs would contain references to subquery rels being modified. */ if (final_rtable != NIL && subqueryRTindexes != NULL) { ListCell *lr; rti = 1; - foreach(lr, parent_parse->rtable) + foreach(lr, orig_rtable) { RangeTblEntry *rte = lfirst_node(RangeTblEntry, lr); if (bms_is_member(rti, subqueryRTindexes)) { Index newrti; + ListCell *lc2; /* * The RTE can't contain any references to its own RT @@ -1473,19 +1513,20 @@ inheritance_planner(PlannerInfo *root) newrti = list_length(subroot->parse->rtable) + 1; ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); ChangeVarNodes((Node *) subroot->rowMarks, rti, newrti, 0); - /* Skip processing unchanging parts of append_rel_list */ - if (modifiableARIindexes != NULL) + /* + * UNION ALL related appinfos are the beginning of the + * list, only change those. + */ + foreach(lc2, subroot->append_rel_list) { - ListCell *lc2; + AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, + lc2); - foreach(lc2, subroot->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, - modifiableARIindexes)) - ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); - } + if (bms_is_member(appinfo2->child_relid, + modifiableARIindexes)) + ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); + else + break; } rte = copyObject(rte); ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); @@ -1514,6 +1555,54 @@ inheritance_planner(PlannerInfo *root) subpath = sub_final_rel->cheapest_total_path; /* + * If we finished planning our first child query, copy the source + * child objects that were added during its planning. + */ + if (source_appinfos == NIL && subroot->append_rel_list) + { + int num_skip_appinfos = list_length(orig_append_rel_list); + int num_skip_rowmarks = list_length(orig_row_marks); + int num_skip_rtes = list_length(rtable_with_target); + ListCell *lc2; + + source_appinfos = list_copy_tail(subroot->append_rel_list, + num_skip_appinfos); + Assert(source_child_rowmarks == NIL); + source_child_rowmarks = list_copy_tail(subroot->rowMarks, + num_skip_rowmarks); + Assert(source_child_rtes == NIL); + source_child_rtes = list_copy_tail(subroot->parse->rtable, + num_skip_rtes); + + /* + * Original parent PlanRowMark is modified when adding the + * child PlanRowMarks. Copy those changes so that the planning + * of subsequent child queries works correctly. That is + * necessary, because we won't be adding child objects again, + * so there won't be an opportunity to modify the parent + * PlanRowMark as desired. + * + * All the original parent row marks are the beginning of + * subroot->rowMarks, skip the rest. + */ + foreach(lc2, orig_row_marks) + { + PlanRowMark *oldrc = lfirst_node(PlanRowMark, lc2); + ListCell *lc3; + + foreach(lc3, subroot->rowMarks) + { + PlanRowMark *newrc = lfirst_node(PlanRowMark, lc3); + + if (oldrc->rti == newrc->rti) + oldrc->allMarkTypes = newrc->allMarkTypes; + else + break; + } + } + } + + /* * If this child rel was excluded by constraint exclusion, exclude it * from the result plan. */ @@ -2618,7 +2707,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2643,7 +2732,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } @@ -7046,6 +7135,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -7147,6 +7240,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 0213a37670..154ccda432 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -326,6 +326,12 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) UPPERREL_FINAL, NULL))) add_rtes_to_flat_rtable(rel->subroot, true); } + /* + * A NULL rel also means an unplanned subquery rte, so apply + * flatten_unplanned_rtes. + */ + else + flatten_unplanned_rtes(glob, rte); } rti++; } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 5392d1a561..87565d2be3 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -126,61 +126,23 @@ preprocess_targetlist(PlannerInfo *root) foreach(lc, root->rowMarks) { PlanRowMark *rc = (PlanRowMark *) lfirst(lc); - Var *var; - char resname[32]; - TargetEntry *tle; + List *junk_tles; /* child rels use the same junk attrs as their parents */ if (rc->rti != rc->prti) continue; - if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) - { - /* Need to fetch TID */ - var = makeVar(rc->rti, - SelfItemPointerAttributeNumber, - TIDOID, - -1, - InvalidOid, - 0); - snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - } - if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) - { - /* Need the whole row as a junk var */ - var = makeWholeRowVar(rt_fetch(rc->rti, range_table), - rc->rti, - 0, - false); - snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - } + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which will change the parent row + * mark's allMarkTypes fields. + */ + if (rc->isParent && !root->contains_inherit_children) + continue; - /* If parent of inheritance tree, always fetch the tableoid too. */ - if (rc->isParent) - { - var = makeVar(rc->rti, - TableOidAttributeNumber, - OIDOID, - -1, - InvalidOid, - 0); - snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - } + junk_tles = get_rowmark_junk_tles(root, tlist, rc); + tlist = list_concat(tlist, junk_tles); } /* @@ -434,3 +396,73 @@ get_plan_rowmark(List *rowmarks, Index rtindex) } return NULL; } + +/* + * add_rowmark_junk_columns + * Add necessary junk columns for rowmarked inheritance parent rel. + * + * These values are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark. + */ +List * +get_rowmark_junk_tles(PlannerInfo *root, List *tlist, PlanRowMark *rc) +{ + List *range_table = root->parse->rtable; + int tlist_len = list_length(tlist); + List *junk_tles = NIL; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + tlist_len + 1, + pstrdup(resname), + true); + junk_tles = lappend(junk_tles, tle); + tlist_len++; + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(rt_fetch(rc->rti, range_table), + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + tlist_len + 1, + pstrdup(resname), + true); + junk_tles = lappend(junk_tles, tle); + tlist_len++; + } + + /* For inheritance cases, always fetch the tableoid too. */ + if (rc->isParent) + { + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + tlist_len + 1, + pstrdup(resname), + true); + junk_tles = lappend(junk_tles, tle); + } + + return junk_tles; +} diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index a014a12060..e437709fc3 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -18,69 +18,120 @@ #include "access/table.h" #include "catalog/partition.h" #include "catalog/pg_inherits.h" +#include "catalog/pg_type.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" #include "optimizer/inherit.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" +#include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" +#include "parser/parsetree.h" #include "partitioning/partdesc.h" +#include "partitioning/partprune.h" #include "utils/rel.h" -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); +static void expand_regular_inherited_rtentry(PlannerInfo *root, + RangeTblEntry *rte, + Index rti, bool is_target); static void expand_partitioned_rtentry(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos); -static void expand_single_inheritance_child(PlannerInfo *root, - RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p); + RangeTblEntry *parentrte, Index parentRTindex); +static void expand_partitioned_target_rtentry(PlannerInfo *root, + RangeTblEntry *parentrte, Index parentRTindex); +static void add_inheritance_child_rel(PlannerInfo *root, + RangeTblEntry *parentrte, + Index parentRTindex, Relation parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p, + AppendRelInfo **appinfo_p); static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, List *translated_vars); /* - * expand_inherited_tables - * Expand each rangetable entry that represents an inheritance set - * into an "append relation". At the conclusion of this process, - * the "inh" flag is set in all and only those RTEs that are append - * relation parents. + * expand_inherited_rtentry + * This initializes RelOptInfos for inheritance child relations if the + * passed-in relation has any + * + * Passed in relation could either be a subquery (if a UNION ALL subquery was + * flattened) or a table that's known to have (or once had) inheritance + * children. The latter consists of both regular inheritance parents and + * partitioned tables. + * + * For a subquery parent, there is not much to be done here because the + * children's RTEs are already present in the query, so we just initialize + * RelOptInfos for them. Also, the AppendRelInfos for child subqueries + * have already been added. + * + * For tables, we need to add the children to the range table and initialize + * AppendRelInfos, RelOptInfos, and PlanRowMarks (if any) for them. For + * a partitioned parent, we only add the children remaining after pruning. + * For regular inheritance parents, we find the children using + * find_all_inheritors and add all of them. + * + * If it turns out that there are no children, then we set rte->inh to false + * to let the caller know that only the parent table needs to be scanned. The + * caller can accordingly switch to a non-Append path. For a partitioned + * parent, that means an empty relation because parents themselves contain no + * data. + * + * For the regular inheritance case, the parent also gets another RTE with + * inh = false to represent it as a child to be scanned as part of the + * inheritance set. The original RTE is considered to represent the whole + * inheritance set. */ void -expand_inherited_tables(PlannerInfo *root) +expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) { - Index nrtes; - Index rti; - ListCell *rl; + /* Only inheritance parent (partitioned or not) are allowed. */ + Assert(rte->rtekind == RTE_RELATION && rte->inh); /* - * expand_inherited_rtentry may add RTEs to parse->rtable. The function is - * expected to recursively handle any RTEs that it creates with inh=true. - * So just scan as far as the original end of the rtable list. + * subquery_planner() must have set rte->inh if the following wasn't true. */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) - { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); + Assert(has_subclass(rte->relid)); - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); - } + /* + * The rewriter should already have obtained an appropriate lock on + * each relation named in the query, so we can open the parent relation + * without locking it. However, for each child relation we add to the + * query, we must obtain an appropriate lock, because this will be the + * first use of those relations in the parse/rewrite/plan pipeline. + * Child rels should use the same lockmode as their parent. + */ + Assert(rte->rellockmode != NoLock); + + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_rtentry(root, rte, rti); + else + expand_regular_inherited_rtentry(root, rte, rti, false); +} + +void +expand_inherited_target_rtentry(PlannerInfo *root) +{ + Index resultRelation = root->parse->resultRelation; + RangeTblEntry *rte = rt_fetch(resultRelation, root->parse->rtable); + + Assert(resultRelation > 0); + Assert(rte->rtekind == RTE_RELATION); + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + expand_partitioned_target_rtentry(root, rte, resultRelation); + else + expand_regular_inherited_rtentry(root, rte, resultRelation, true); } /* - * expand_inherited_rtentry - * Check whether a rangetable entry represents an inheritance set. - * If so, add entries for all the child tables to the query's - * rangetable, and build AppendRelInfo nodes for all the child tables - * and add them to root->append_rel_list. If not, clear the entry's - * "inh" flag to prevent later code from looking for AppendRelInfos. + * expand_regular_inherited_rtentry + * Add entries for all the child tables to the query's rangetable, and + * build AppendRelInfo nodes for all the child tables and add them to + * root->append_rel_list. * * Note that the original RTE is considered to represent the whole * inheritance set. The first of the generated RTEs is an RTE for the same @@ -91,269 +142,339 @@ expand_inherited_tables(PlannerInfo *root) * regular inheritance, a parent RTE must always have at least two associated * AppendRelInfos: one corresponding to the parent table as a simple member of * inheritance set and one or more corresponding to the actual children. - * Since a partitioned table is not scanned, it might have only one associated - * AppendRelInfo. */ static void -expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) +expand_regular_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, + Index rti, bool is_target) { Oid parentOID; - PlanRowMark *oldrc; - Relation oldrelation; - LOCKMODE lockmode; + Relation parentrel; + PlanRowMark *oldrc = NULL; + LOCKMODE lockmode = rte->rellockmode; List *inhOIDs; ListCell *l; + int num_children; + int num_children_added = 0; + RelOptInfo *parent = NULL; - /* Does RT entry allow inheritance? */ - if (!rte->inh) - return; - /* Ignore any already-expanded UNION ALL nodes */ - if (rte->rtekind != RTE_RELATION) - { - Assert(rte->rtekind == RTE_SUBQUERY); - return; - } - /* Fast path for common case of childless table */ + Assert(rte->rtekind == RTE_RELATION); + Assert(lockmode != NoLock); parentOID = rte->relid; - if (!has_subclass(parentOID)) + + /* Scan for all members of inheritance set, acquire needed locks */ + inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); + + /* + * Check that there's at least one descendant, else treat as no-child + * case. This could happen despite has_subclass() check performed by + * subquery_planner, if table once had a child but no longer does. + */ + num_children = list_length(inhOIDs); + if (num_children < 2) { /* Clear flag before returning */ rte->inh = false; return; } - /* - * The rewriter should already have obtained an appropriate lock on each - * relation named in the query, so we can open the parent relation without - * locking it. However, for each child relation we add to the query, we - * must obtain an appropriate lock, because this will be the first use of - * those relations in the parse/rewrite/plan pipeline. Child rels should - * use the same lockmode as their parent. - */ - oldrelation = table_open(parentOID, NoLock); - lockmode = rte->rellockmode; + /* Already locked. */ + parentrel = table_open(parentOID, NoLock); /* - * If parent relation is selected FOR UPDATE/SHARE, we need to mark its - * PlanRowMark as isParent = true, and generate a new PlanRowMark for each - * child. + * For inherited update/delete, there's no RelOptInfo for parent and we + * won't be creating one for the children either, so the following info + * is unnecessary. */ - oldrc = get_plan_rowmark(root->rowMarks, rti); - if (oldrc) - oldrc->isParent = true; - - /* Scan the inheritance set and expand it */ - if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + if (!is_target) { - Assert(rte->relkind == RELKIND_PARTITIONED_TABLE); + /* + * If parent relation is selected FOR UPDATE/SHARE, + * preprocess_rowmarks should've set isParent = true. We'll generate + * a new PlanRowMark for each child. + */ + oldrc = get_plan_rowmark(root->rowMarks, rti); + Assert(oldrc == NULL || oldrc->isParent); + + parent = find_base_rel(root, rti); /* - * If this table has partitions, recursively expand and lock them. - * While at it, also extract the partition key columns of all the - * partitioned tables. + * Must expand PlannerInfo arrays by num_children before we can add + * children. */ - expand_partitioned_rtentry(root, rte, rti, oldrelation, oldrc, - lockmode, &root->append_rel_list); + Assert(num_children > 0); + expand_planner_arrays(root, num_children); } - else + + foreach(l, inhOIDs) { - List *appinfos = NIL; + Oid childOID = lfirst_oid(l); + /* Already locked. */ + Relation childrel = table_open(childOID, NoLock); RangeTblEntry *childrte; Index childRTindex; - - /* Scan for all members of inheritance set, acquire needed locks */ - inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); + AppendRelInfo *appinfo; /* - * Check that there's at least one descendant, else treat as no-child - * case. This could happen despite above has_subclass() check, if the - * table once had a child but no longer does. + * It is possible that the parent table has children that are temp + * tables of other backends. We cannot safely access such tables + * (because of buffering issues), and the best thing to do seems + * to be to silently ignore them. */ - if (list_length(inhOIDs) < 2) + if (childOID != parentOID && RELATION_IS_OTHER_TEMP(childrel)) { - /* Clear flag before returning */ - rte->inh = false; - heap_close(oldrelation, NoLock); - return; + table_close(childrel, lockmode); + continue; } - /* - * This table has no partitions. Expand any plain inheritance - * children in the order the OIDs were returned by - * find_all_inheritors. - */ - foreach(l, inhOIDs) + /* Add RTE, AppendRelInfo, PlanRowMark for the child. */ + add_inheritance_child_rel(root, rte, rti, parentrel, oldrc, + childrel, &childrte, &childRTindex, + &appinfo); + Assert(childrte != NULL && childRTindex > 0 && appinfo != NULL); + + /* Close child relations, but keep locks */ + table_close(childrel, NoLock); + num_children_added++; + + /* All regular inheritance children are leaf children. */ + Assert(!childrte->inh); + + /* Also create the RelOptInfo in the non-target inheritance case. */ + if (parent) { - Oid childOID = lfirst_oid(l); - Relation newrelation; + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; - /* Open rel if needed; we already have required locks */ - if (childOID != parentOID) - newrelation = table_open(childOID, NoLock); - else - newrelation = oldrelation; - - /* - * It is possible that the parent table has children that are temp - * tables of other backends. We cannot safely access such tables - * (because of buffering issues), and the best thing to do seems - * to be to silently ignore them. - */ - if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation)) - { - table_close(newrelation, lockmode); - continue; - } - - expand_single_inheritance_child(root, rte, rti, oldrelation, oldrc, - newrelation, - &appinfos, &childrte, - &childRTindex); - - /* Close child relations, but keep locks */ - if (childOID != parentOID) - table_close(newrelation, NoLock); + /* Add the RelOptInfo too. */ + (void) build_simple_rel(root, childRTindex, parent); } - - /* - * If all the children were temp tables, pretend it's a - * non-inheritance situation; we don't need Append node in that case. - * The duplicate RTE we added for the parent table is harmless, so we - * don't bother to get rid of it; ditto for the useless PlanRowMark - * node. - */ - if (list_length(appinfos) < 2) - rte->inh = false; - else - root->append_rel_list = list_concat(root->append_rel_list, - appinfos); - } - table_close(oldrelation, NoLock); + /* + * If all the children except the parent itself in its role as a child + * were temp tables of other backends or were excluded, pretend it's a + * non-inheritance situation; we don't need Append node in that case. The + * duplicate RTE we added for the parent table is harmless, so we don't + * bother to get rid of it; ditto for the useless PlanRowMark node. + */ + if (num_children_added == 1) + rte->inh = false; + + if (parent) + { + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = root->processed_tlist; + List *junk_tles = get_rowmark_junk_tles(root, tlist, oldrc); + + build_base_rel_tlists(root, junk_tles); + root->processed_tlist = list_concat(root->processed_tlist, + junk_tles); + } + } + + table_close(parentrel, NoLock); } /* * expand_partitioned_rtentry - * Recursively expand an RTE for a partitioned table. + * Add partitions to Query and PlannerInfo + * + * If we're expanding source inheritance, there's enought information to + * perform partition pruning, so only add those that survive pruning. + * + * Partitions are locked and added to the query in order in which they are + * found in the parent's PartitionDesc. */ static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, LOCKMODE lockmode, - List **appinfos) + Index parentRTindex) { + Relation parentrel; + PartitionDesc partdesc; + LOCKMODE lockmode = parentrte->rellockmode; int i; - RangeTblEntry *childrte; - Index childRTindex; - PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); + RelOptInfo *parent = find_base_rel(root, parentRTindex); + PlanRowMark *oldrc = NULL; + Bitmapset *partindexes; - check_stack_depth(); + /* Already locked. */ + parentrel = table_open(parentrte->relid, NoLock); + partdesc = RelationGetPartitionDesc(parentrel); - /* A partitioned table should always have a partition descriptor. */ - Assert(partdesc); + /* Perform pruning. */ + partindexes = prune_append_rel_partitions(parent); - Assert(parentrte->inh); + /* Must expand PlannerInfo arrays before we can add children. */ + if (bms_num_members(partindexes) > 0) + expand_planner_arrays(root, bms_num_members(partindexes)); /* - * Note down whether any partition key cols are being updated. Though it's - * the root partitioned table's updatedCols we are interested in, we - * instead use parentrte to get the updatedCols. This is convenient - * because parentrte already has the root partrel's updatedCols translated - * to match the attribute ordering of parentrel. + * For partitioned tables, we also store the partition RelOptInfo + * pointers in the parent's RelOptInfo. */ - if (!root->partColsUpdated) - root->partColsUpdated = - has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + parent->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * + parent->nparts); - /* First expand the partitioned table itself. */ - expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, - top_parentrc, parentrel, - appinfos, &childrte, &childRTindex); - - /* - * If the partitioned table has no partitions, treat this as the - * non-inheritance case. - */ - if (partdesc->nparts == 0) + oldrc = get_plan_rowmark(root->rowMarks, parentRTindex); + Assert(oldrc == NULL || oldrc->isParent); + i = -1; + while ((i = bms_next_member(partindexes, i)) >= 0) { - parentrte->inh = false; - return; + Oid childOID = partdesc->oids[i]; + /* Open rel, acquiring required locks */ + Relation childrel = table_open(childOID, lockmode); + RangeTblEntry *childrte = NULL; + Index childRTindex = 0; + AppendRelInfo *appinfo = NULL; + + /* + * A partitioned child table with 0 children is a dummy rel, so + * don't bother creating any planner objects for it. + */ + if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(childrel)->nparts == 0) + { + table_close(childrel, NoLock); + continue; + } + + add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, oldrc, childrel, &childrte, + &childRTindex, &appinfo); + Assert(childrte != NULL && childRTindex > 0 && appinfo != NULL); + table_close(childrel, NoLock); + + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + + /* Add the RelOptInfo too. */ + parent->part_rels[i] = build_simple_rel(root, childRTindex, parent); + + /* If the child is partitioned itself, expand it too. */ + if (childrte->inh) + { + Assert(parent->part_rels[i]->part_scheme != NULL); + expand_partitioned_rtentry(root, childrte, childRTindex); + } } + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc && oldrc->rti == oldrc->prti) + { + List *tlist = root->processed_tlist; + List *junk_tles = get_rowmark_junk_tles(root, tlist, oldrc); + + build_base_rel_tlists(root, junk_tles); + root->processed_tlist = list_concat(root->processed_tlist, junk_tles); + } + + table_close(parentrel, NoLock); +} + +static void +expand_partitioned_target_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex) +{ + Relation parentrel; + PartitionDesc partdesc; + LOCKMODE lockmode = parentrte->rellockmode; + int i; + + /* Already locked. */ + parentrel = table_open(parentrte->relid, NoLock); + partdesc = RelationGetPartitionDesc(parentrel); + + /* + * For inherited update/delete query, just add RTEs and AppendRelInfos + * for *all* partitions. + */ for (i = 0; i < partdesc->nparts; i++) { Oid childOID = partdesc->oids[i]; - Relation childrel; - /* Open rel, acquiring required locks */ - childrel = table_open(childOID, lockmode); + Relation childrel = table_open(childOID, lockmode); + RangeTblEntry *childrte = NULL; + Index childRTindex = 0; + AppendRelInfo *appinfo = NULL; /* - * Temporary partitions belonging to other sessions should have been - * disallowed at definition, but for paranoia's sake, let's double - * check. + * A partitioned child table with 0 children is a dummy rel, so + * don't bother creating any planner objects for it. */ - if (RELATION_IS_OTHER_TEMP(childrel)) - elog(ERROR, "temporary relation from another session found as partition"); + if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + RelationGetPartitionDesc(childrel)->nparts == 0) + { + table_close(childrel, NoLock); + continue; + } - expand_single_inheritance_child(root, parentrte, parentRTindex, - parentrel, top_parentrc, childrel, - appinfos, &childrte, &childRTindex); - - /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - expand_partitioned_rtentry(root, childrte, childRTindex, - childrel, top_parentrc, lockmode, - appinfos); - - /* Close child relation, but keep locks */ + /* Adds RTE and AppendRelInfo for the child. */ + add_inheritance_child_rel(root, parentrte, parentRTindex, + parentrel, NULL, childrel, + &childrte, &childRTindex, &appinfo); + Assert(childrte != NULL && childRTindex > 0 && appinfo != NULL); table_close(childrel, NoLock); + + /* Handle sub-partitioned child recursively. */ + if (childrte->inh) + expand_partitioned_target_rtentry(root, childrte, childRTindex); } + + /* + * Note whether partition key columns are updated; due to recursion, + * also considers sub-partitioned children's partition key being + * updated. + */ + root->partColsUpdated |= has_partition_attrs(parentrel, + parentrte->updatedCols, + NULL); + + table_close(parentrel, NoLock); } /* - * expand_single_inheritance_child - * Build a RangeTblEntry and an AppendRelInfo, if appropriate, plus - * maybe a PlanRowMark. - * - * We now expand the partition hierarchy level by level, creating a - * corresponding hierarchy of AppendRelInfos and RelOptInfos, where each - * partitioned descendant acts as a parent of its immediate partitions. - * (This is a difference from what older versions of PostgreSQL did and what - * is still done in the case of table inheritance for unpartitioned tables, - * where the hierarchy is flattened during RTE expansion.) + * add_inheritance_child_rel + * Build a RangeTblEntry, an AppendRelInfo, a PlanRowMark, and finally + * a RelOptInfo for an inheritance child relation. * * PlanRowMarks still carry the top-parent's RTI, and the top-parent's * allMarkTypes field still accumulates values from all descendents. * - * "parentrte" and "parentRTindex" are immediate parent's RTE and - * RTI. "top_parentrc" is top parent's PlanRowMark. - * - * The child RangeTblEntry and its RTI are returned in "childrte_p" and - * "childRTindex_p" resp. + * "parentrte" and "parentRTindex" are immediate parent's RTE and RTI, resp. + * "top_parentrc" is top parent's PlanRowMark. */ static void -expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, - Index parentRTindex, Relation parentrel, - PlanRowMark *top_parentrc, Relation childrel, - List **appinfos, RangeTblEntry **childrte_p, - Index *childRTindex_p) +add_inheritance_child_rel(PlannerInfo *root, RangeTblEntry *parentrte, + Index parentRTindex, Relation parentrel, + PlanRowMark *top_parentrc, Relation childrel, + RangeTblEntry **childrte_p, Index *childRTindex_p, + AppendRelInfo **appinfo_p) { Query *parse = root->parse; - Oid parentOID = RelationGetRelid(parentrel); Oid childOID = RelationGetRelid(childrel); - RangeTblEntry *childrte; Index childRTindex; + RangeTblEntry *childrte; AppendRelInfo *appinfo; /* * Build an RTE for the child, and attach to query's rangetable list. We * copy most fields of the parent's RTE, but replace relation OID and - * relkind, and set inh = false. Also, set requiredPerms to zero since - * all required permissions checks are done on the original RTE. Likewise, - * set the child's securityQuals to empty, because we only want to apply - * the parent's RLS conditions regardless of what RLS properties + * relkind, and set inh appropriately. Also, set requiredPerms to zero + * since all required permissions checks are done on the original RTE. + * Likewise, set the child's securityQuals to empty, because we only want + * to apply the parent's RLS conditions regardless of what RLS properties * individual children may have. (This is an intentional choice to make * inherited RLS work like regular permissions checks.) The parent * securityQuals will be propagated to children along with other base @@ -363,49 +484,40 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childrte_p = childrte; childrte->relid = childOID; childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ - if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) - childrte->inh = true; - else - childrte->inh = false; + /* + * A partitioned child will need to be expanded as an append parent + * itself, so set its inh to true. + */ + childrte->inh = (childrte->relkind == RELKIND_PARTITIONED_TABLE); childrte->requiredPerms = 0; childrte->securityQuals = NIL; parse->rtable = lappend(parse->rtable, childrte); childRTindex = list_length(parse->rtable); *childRTindex_p = childRTindex; - /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. - */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) - { - appinfo = make_append_rel_info(parentrel, childrel, - parentRTindex, childRTindex); - *appinfos = lappend(*appinfos, appinfo); + /* Create an AppendRelInfo and add it to planner's global list. */ + appinfo = make_append_rel_info(parentrel, childrel, parentRTindex, + childRTindex); + root->append_rel_list = lappend(root->append_rel_list, appinfo); + *appinfo_p = appinfo; - /* - * Translate the column permissions bitmaps to the child's attnums (we - * have to build the translated_vars list before we can do this). But - * if this is the parent table, leave copyObject's result alone. - * - * Note: we need to do this even though the executor won't run any - * permissions checks on the child RTE. The insertedCols/updatedCols - * bitmaps may be examined for trigger-firing purposes. - */ - if (childOID != parentOID) - { - childrte->selectedCols = translate_col_privs(parentrte->selectedCols, - appinfo->translated_vars); - childrte->insertedCols = translate_col_privs(parentrte->insertedCols, - appinfo->translated_vars); - childrte->updatedCols = translate_col_privs(parentrte->updatedCols, - appinfo->translated_vars); - } + /* + * Translate the column permissions bitmaps to the child's attnums (we + * have to build the translated_vars list before we can do this). But + * if this is the parent table, leave copyObject's result alone. + * + * Note: we need to do this even though the executor won't run any + * permissions checks on the child RTE. The insertedCols/updatedCols + * bitmaps may be examined for trigger-firing purposes. + */ + if (childrte->relid != parentrte->relid) + { + childrte->selectedCols = translate_col_privs(parentrte->selectedCols, + appinfo->translated_vars); + childrte->insertedCols = translate_col_privs(parentrte->insertedCols, + appinfo->translated_vars); + childrte->updatedCols = translate_col_privs(parentrte->updatedCols, + appinfo->translated_vars); } /* diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 78a96b4ee2..b6d4725a31 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -27,6 +27,7 @@ #include "catalog/catalog.h" #include "catalog/dependency.h" #include "catalog/heap.h" +#include "catalog/partition.h" #include "catalog/pg_am.h" #include "catalog/pg_proc.h" #include "catalog/pg_statistic_ext.h" @@ -107,20 +108,20 @@ static void set_baserel_partition_key_exprs(Relation relation, * important for it. */ void -get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, - RelOptInfo *rel) +get_relation_info(PlannerInfo *root, RangeTblEntry *rte, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; bool hasindex; List *indexinfos = NIL; + bool inhparent = rte->inh; /* * We need not lock the relation since it was already locked, either by * the rewriter or when expand_inherited_rtentry() added it to the query's * rangetable. */ - relation = table_open(relationObjectId, NoLock); + relation = heap_open(rte->relid, NoLock); /* Temporary and unlogged relations are inaccessible during recovery. */ if (!RelationNeedsWAL(relation) && RecoveryInProgress()) @@ -460,7 +461,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, * removing an index, or adding a hypothetical index to the indexlist. */ if (get_relation_info_hook) - (*get_relation_info_hook) (root, relationObjectId, inhparent, rel); + (*get_relation_info_hook) (root, rte->relid, rte->inh, rel); } /* @@ -2082,16 +2083,19 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; + + /* + * Since we must've taken a lock on the table, it's okay to simply copy + * the pointers to relcache data here. + */ + rel->boundinfo = partdesc->boundinfo; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); } diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 4130514952..c5ab9ca8df 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -16,19 +16,26 @@ #include <limits.h> +#include "access/table.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" +#include "optimizer/inherit.h" +#include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/placeholder.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" #include "partitioning/partbounds.h" +#include "partitioning/partdesc.h" #include "utils/hsearch.h" +#include "utils/rel.h" typedef struct JoinHashEntry @@ -63,6 +70,9 @@ static void build_child_join_reltarget(PlannerInfo *root, RelOptInfo *childrel, int nappinfos, AppendRelInfo **appinfos); +static bool apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *childrel, RangeTblEntry *childRTE, + AppendRelInfo *appinfo); /* @@ -132,6 +142,50 @@ setup_append_rel_array(PlannerInfo *root) } /* + * expand_planner_arrays + * Expand the PlannerInfo arrays by add_size members and initialize the + * the newly added bytes with zero + */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size; + + Assert(add_size > 0); + + new_size = root->simple_rel_array_size + add_size; + + /* Expand various arrays and 0-initialize added bytes. */ + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + + if (root->append_rel_array) + { + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + } + else + { + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + } + + root->simple_rel_array_size = new_size; +} + +/* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. */ @@ -195,6 +249,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) rel->joininfo = NIL; rel->has_eclass_joins = false; rel->consider_partitionwise_join = false; /* might get changed later */ + rel->top_parent_relids = NULL; /* might be changed later */ rel->part_scheme = NULL; rel->nparts = 0; rel->boundinfo = NULL; @@ -217,15 +272,13 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) else rel->top_parent_relids = bms_copy(parent->relids); } - else - rel->top_parent_relids = NULL; /* Check type of rtable entry */ switch (rte->rtekind) { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ - get_relation_info(root, rte->relid, rte->inh, rel); + get_relation_info(root, rte, rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: @@ -274,55 +327,287 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) list_length(rte->securityQuals)); /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. + * Add the parent to partitioned_child_rels. + * + * Note that during the set_append_rel_pathlist() phase, values of the + * indexes of partitioned relations that appear down in the tree will + * be bubbled up into root parent's list so that when we've created + * Paths for all the children, the root table's list will contain all + * such indexes. */ - if (rte->inh) + if (rel->part_scheme) + rel->partitioned_child_rels = list_make1_int(relid); + + if (parent) { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; + AppendRelInfo *appinfo = root->append_rel_array[relid]; - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); + Assert(appinfo != NULL); - foreach(l, root->append_rel_list) + /* + * Propagate lateral_relids and lateral_referencers from appendrel + * parent rels to their child rels. We intentionally give each child + * rel the same minimum parameterization, even though it's quite + * possible that some don't reference all the lateral rels. This is + * because any append path for the parent will have to have the same + * parameterization for every child anyway, and there's no value in + * forcing extra reparameterize_path() calls. Similarly, a lateral + * reference to the parent prevents use of otherwise-movable join rels + * for each child. + * + * It's possible for child rels to have their own children, in which + * case the topmost parent's lateral info must be propagated all the + * way down. That's ensured by having childrel be expanded via this + * same path. + */ + rel->direct_lateral_relids = parent->direct_lateral_relids; + rel->lateral_relids = parent->lateral_relids; + rel->lateral_referencers = parent->lateral_referencers; + + /* + * We have to copy the parent's quals to the child, with appropriate + * substitution of variables. However, only the baserestrictinfo + * quals are needed before we can check for constraint exclusion; so + * do that first and then check to see if we can disregard this child. + */ + if (!apply_child_basequals(root, parent, rel, rte, appinfo)) { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. Note that + * apply_child_basequals won't have copied quals in this case. */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; + set_dummy_rel_pathlist(rel); } - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); + /* Constraint exclusion. */ + if (relation_excluded_by_constraints(root, rel, rte)) + set_dummy_rel_pathlist(rel); } return rel; } /* + * apply_child_basequals + * Populate childrel's quals based on rel's quals, translating them using + * appinfo. + * + * If any of the resulting clauses evaluate to false or NULL, we return false + * and don't apply any quals. Caller can mark the relation as a dummy rel in + * this case, since it needn't be scanned. + * + * If any resulting clauses evaluate to true, they're unnecessary and we don't + * apply then. + */ +static bool +apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *childrel, RangeTblEntry *childRTE, + AppendRelInfo *appinfo) +{ + List *childquals; + Index cq_min_security; + ListCell *lc; + + /* + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities for + * const-simplification, and perhaps even pseudoconstant quals. Therefore, + * transform each RestrictInfo separately to see if it reduces to a + * constant or pseudoconstant. (We must process them separately to keep + * track of the security level of each qual.) + */ + childquals = NIL; + cq_min_security = UINT_MAX; + foreach(lc, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, + (Node *) rinfo->clause, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + return false; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + /* track minimum security level among child quals */ + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might have + * securityQuals associated with this particular child node. (Currently + * this can only happen in appendrels originating from UNION ALL; + * inheritance child tables don't have their own securityQuals, see + * expand_inherited_rtentry().) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except that we + * can't make any general deductions from such quals, since they don't + * hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* not likely that we'd see constants here, so no check */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, false, false, + security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* + * OK, we've got all the baserestrictinfo quals for this child. + */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + return true; +} + +/* + * add_appendrel_other_rels + * + */ +void +add_appendrel_other_rels(PlannerInfo *root, RangeTblEntry *rte, Index rti) +{ + ListCell *l; + RelOptInfo *rel; + Relation relation = NULL; + PartitionDesc partdesc; + + /* + * If a table, expand_inherited_rtentry takes care of recursively adds + * all the otherrels, performing partition pruning for both this + * rel and recursively added partitioned tables. + */ + if (rte->rtekind == RTE_RELATION && !root->contains_inherit_children) + { + expand_inherited_rtentry(root, rte, rti); + return; + } + + /* + * UNION ALL children already got RTEs and AppendRelInfos, so just build + * RelOptInfos and return. + */ + rel = find_base_rel(root, rti); + + if (rel->part_scheme) + { + rel->part_rels = (RelOptInfo **) + palloc0(sizeof(RelOptInfo *) * rel->nparts); + relation = table_open(rte->relid, NoLock); + partdesc = RelationGetPartitionDesc(relation); + } + + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + RangeTblEntry *childrte; + RelOptInfo *childrel; + int i = 0; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + childrte = root->simple_rte_array[childRTindex]; + Assert(childrte != NULL); + childrel = build_simple_rel(root, childRTindex, rel); + + /* Nothing more to do for an unpartitioned table. */ + if (rel->part_scheme != NULL) + { + /* + * Add childrel to part_rels array. Considering that + * append_rel_list may not contain all partitions due to some + * being pruned, we must skip the slots of part_rels array + * that would otherwise contain pruned partitions. This works + * because, partitions appear in append_rel_list in the same + * order as the order in which they appear in the PartitionDesc. + * See expand_partitioned_rtentry. + */ + Assert(partdesc != NULL); + for (i = 0; i < partdesc->nparts; i++) + { + if (childrte->relid == partdesc->oids[i]) + { + rel->part_rels[i] = childrel; + break; + } + } + } + + /* Child may itself be an inherited relation. */ + if (childrte->inh) + add_appendrel_other_rels(root, childrte, childRTindex); + } + + if (relation) + table_close(relation, NoLock); +} + +/* * find_base_rel * Find a base or other relation entry, which must already exist. */ diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 8c9721935d..472a6cd331 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -45,6 +45,8 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" +#include "optimizer/cost.h" #include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "partitioning/partprune.h" @@ -433,17 +435,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -537,23 +545,20 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ @@ -561,6 +566,13 @@ prune_append_rel_partitions(RelOptInfo *rel) return NULL; /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. */ @@ -587,15 +599,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index a008ae07da..c2e292e54c 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -348,6 +348,13 @@ struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* + * Does this PlannerInfo and its Query object contain *all* inheritance + * children? If true, the RTEs, the AppendRelInfos, and the PlanRowMarks + * of all the children are assumed to be present. + */ + bool contains_inherit_children; }; diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h index d2418f15cf..bf3dd357d6 100644 --- a/src/include/optimizer/inherit.h +++ b/src/include/optimizer/inherit.h @@ -16,7 +16,8 @@ #include "nodes/pathnodes.h" - -extern void expand_inherited_tables(PlannerInfo *root); +extern void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, + Index rti); +extern void expand_inherited_target_rtentry(PlannerInfo *root); #endif /* INHERIT_H */ diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 574bb85b50..60361507d2 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -277,8 +277,11 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); +extern void add_appendrel_other_rels(PlannerInfo *root, RangeTblEntry *rte, + Index rti); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); extern RelOptInfo *find_join_rel(PlannerInfo *root, Relids relids); extern RelOptInfo *build_join_rel(PlannerInfo *root, diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index c337f047cb..04731f532f 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -25,8 +25,8 @@ typedef void (*get_relation_info_hook_type) (PlannerInfo *root, extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; -extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, - bool inhparent, RelOptInfo *rel); +extern void get_relation_info(PlannerInfo *root, RangeTblEntry *rte, + RelOptInfo *rel); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index 3bbdb5e2f7..035caac500 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -65,6 +65,7 @@ extern int from_collapse_limit; extern int join_collapse_limit; extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode); +extern void add_other_rels_to_query(PlannerInfo *root, Node *jtnode); extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist); extern void add_vars_to_targetlist(PlannerInfo *root, List *vars, Relids where_needed, bool create_new_ph); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index a9b2c9026c..5979c9885e 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -37,6 +37,8 @@ extern Relids get_relids_for_join(Query *query, int joinrelid); extern List *preprocess_targetlist(PlannerInfo *root); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); +extern List *get_rowmark_junk_tles(PlannerInfo *root, List *tlist, + PlanRowMark *rc); /* * prototypes for prepunion.c diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index 6bc106831e..1450cef057 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -144,7 +144,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE 1 = 2 GROUP BY c; QUERY PLAN -------------------------------- HashAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) @@ -159,7 +159,7 @@ SELECT c, sum(a) FROM pagg_tab WHERE c = 'x' GROUP BY c; QUERY PLAN -------------------------------- GroupAggregate - Group Key: pagg_tab.c + Group Key: c -> Result One-Time Filter: false (4 rows) -- 2.11.0
v25-0002-Teach-planner-to-only-process-unpruned-partition.patch (text/plain, 7.1K) download From e9d3d5d8ea899f1fd028cb5b628337197d1b2b32 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 15 Oct 2018 10:59:24 +0900 Subject: [PATCH v25 2/2] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/inherit.c | 1 + src/backend/optimizer/util/relnode.c | 4 ++++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/pathnodes.h | 4 ++++ 6 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 66a5cf3334..57158d90ec 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1470,6 +1470,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } if (baserel1) diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index ddc4d27f67..bcfa84b4ce 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -7127,7 +7127,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -7135,9 +7137,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -7218,7 +7218,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -7230,7 +7229,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -7240,9 +7241,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index e437709fc3..e1a8a35561 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -310,6 +310,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, /* Perform pruning. */ partindexes = prune_append_rel_partitions(parent); + parent->live_parts = partindexes; /* Must expand PlannerInfo arrays before we can add children. */ if (bms_num_members(partindexes) > 0) diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index c5ab9ca8df..6d8b862535 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -593,6 +593,7 @@ add_appendrel_other_rels(PlannerInfo *root, RangeTblEntry *rte, Index rti) if (childrte->relid == partdesc->oids[i]) { rel->part_rels[i] = childrel; + rel->live_parts = bms_add_member(rel->live_parts, i); break; } } @@ -2019,6 +2020,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 472a6cd331..babb7d3406 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -438,29 +438,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index c2e292e54c..53d0485964 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -712,6 +712,10 @@ typedef struct RelOptInfo List *partition_qual; /* partition constraint */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Indexes into part_rels of the non-NULL + * RelOptInfos of unpruned partitions; exists + * to avoid having to iterate over the entire + * part_rels array to filter NULL entries. */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-03-01 13:03  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  2 siblings, 0 replies; 265+ messages in thread

From: Amit Langote @ 2019-03-01 13:03 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: 'Amit Langote' <amitlangote09@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Justin Pryzby <pryzby@telsasoft.com>; pgsql-hackers

On 2019/03/01 22:01, Amit Langote wrote:
> Of course, I had to make sure that query_planner (which is not
> in the charge of adding source inheritance child objects) can notice that.

Oops, I meant to write "query_planner (which *is* in the charge of adding
source inheritance child objects)..."

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-03-04 09:14  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  2 siblings, 1 reply; 265+ messages in thread

From: Imai, Yoshikazu @ 2019-03-04 09:14 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Justin Pryzby <pryzby@telsasoft.com>; pgsql-hackers

Amit-san,

On Fri, Mar 1, 2019 at 1:02 PM, Amit Langote wrote:
> Thanks for testing and sorry it took me a while to reply.

Thanks for working about this late at night. I know you have a lot of things to do.

> On 2019/02/25 15:24, Imai, Yoshikazu wrote:
> > [update_pt_with_joining_another_pt.sql]
> > update rt set c = jrt.c + 100 from jrt where rt.b = jrt.b;
> >
> > [pgbench]
> > pgbench -n -f update_pt_with_joining_another_pt_for_ptkey.sql -T 60
> > postgres
> >
> > [results]
> > (part_num_rt, part_num_jrt)  master  patched(0001)
> > ---------------------------  ------  -------------
> >                   (3, 1024)    8.06           5.89
> >                   (3, 2048)    1.52           0.87
> >                   (6, 1024)    4.11           1.77
> >
> >
> >
> > With HEAD, we add target inheritance and source inheritance to
> parse->rtable in inheritance_planner and copy and adjust it for child
> tables at beginning of each planning of child tables.
> >
> > With the 0001 patch, we add target inheritance to parse->rtable in
> inheritance_planner and add source inheritance to parse->rtable in
> make_one_rel(under grouping_planner()) during each planning of child
> tables.
> > Adding source inheritance to parse->rtable may be the same process
> between each planning of child tables and it might be useless. OTOH, from
> the POV of making inheritance-expansion-at-bottom better, expanding
> source inheritance in make_one_rel seems correct design to me.
> >
> > How should we do that...?
> 
> To solve this problem, I ended up teaching inheritance_planner to reuse
> the objects for *source* inheritance child relations (RTEs,
> AppendRelInfos, and PlanRowMarks) created during the planning of the 1st
> child query for the planning of subsequent child queries.  Set of source
> child relations don't change between different planning runs, so it's
> okay to do so.  Of course, I had to make sure that query_planner (which
> is not in the charge of adding source inheritance child objects) can notice
> that.

I did above test again with v25 patch and checked the problem is solved.

[results]
(part_num_rt, part_num_jrt)  master  patched(0001)
---------------------------  ------  -------------
                  (3, 1024)    6.11           6.82
                  (3, 2048)    1.05           1.48
                  (6, 1024)    3.05           3.45

Sorry that I haven't checked the codes related this problem yet, but I'll check it later.


> Please find attached updated patches.  Will update source code comments,
> commit message and perform other fine-tuning over the weekend if possible.

I've taken at glance the codes and there are some minor comments about the patch.

* You changed a usage/arguments of get_relation_info, but why you did it? I made those codes back to the original code and checked it passes make check. So ISTM there are no logical problems with not changing it. Or if you change it, how about also change a usage/arguments of get_relation_info_hook in the same way?

-get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
-				  RelOptInfo *rel)
+get_relation_info(PlannerInfo *root, RangeTblEntry *rte, RelOptInfo *rel)
 {
+	bool		inhparent = rte->inh;
-	relation = table_open(relationObjectId, NoLock);
+	relation = heap_open(rte->relid, NoLock);
  ...
 	if (get_relation_info_hook)
-		(*get_relation_info_hook) (root, relationObjectId, inhparent, rel);
+		(*get_relation_info_hook) (root, rte->relid, rte->inh, rel);


@@ -217,15 +272,13 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 	/* Check type of rtable entry */
 	switch (rte->rtekind)
 	{
 		case RTE_RELATION:
 			/* Table --- retrieve statistics from the system catalogs */
-			get_relation_info(root, rte->relid, rte->inh, rel);
+			get_relation_info(root, rte, rel);


* You moved the codes of initializing of append rel's partitioned_child_rels in set_append_rel_size() to build_simple_rel(), but is it better to do? I also checked the original code passes make check by doing like above.

@@ -954,32 +948,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
 	Assert(IS_SIMPLE_REL(rel));
 
 	/*
-	 * Initialize partitioned_child_rels to contain this RT index.
-	 *
-	 * Note that during the set_append_rel_pathlist() phase, we will bubble up
-	 * the indexes of partitioned relations that appear down in the tree, so
-	 * that when we've created Paths for all the children, the root
-	 * partitioned table's list will contain all such indexes.
-	 */
-	if (rte->relkind == RELKIND_PARTITIONED_TABLE)
-		rel->partitioned_child_rels = list_make1_int(rti);

@@ -274,55 +327,287 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
 										list_length(rte->securityQuals));
 
 	/*
-	 * If this rel is an appendrel parent, recurse to build "other rel"
-	 * RelOptInfos for its children.  They are "other rels" because they are
-	 * not in the main join tree, but we will need RelOptInfos to plan access
-	 * to them.
+	 * Add the parent to partitioned_child_rels.
+	 *
+	 * Note that during the set_append_rel_pathlist() phase, values of the
+	 * indexes of partitioned relations that appear down in the tree will
+	 * be bubbled up into root parent's list so that when we've created
+	 * Paths for all the children, the root table's list will contain all
+	 * such indexes.
 	 */
-	if (rte->inh)
+	if (rel->part_scheme)
+		rel->partitioned_child_rels = list_make1_int(relid);

I'll review rest of codes.

--
Yoshikazu Imai



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-03-04 10:38  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  2 siblings, 2 replies; 265+ messages in thread

From: Amit Langote @ 2019-03-04 10:38 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: 'Amit Langote' <amitlangote09@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Justin Pryzby <pryzby@telsasoft.com>; pgsql-hackers

Hi,

On 2019/03/01 22:01, Amit Langote wrote:
> Please find attached updated patches.  Will update source code comments,
> commit message and perform other fine-tuning over the weekend if possible.

I realized when "fine-tuning" that the patch 0001 contained too many
changes that seem logically separable.  I managed to divide it into the
following patches, which also amounts to a much shorter overall diff
against the master.  Also, the smaller individual patches made it easier
to spot a lot of useless diffs of inherit.c.

Attached patches are as follows:

1. Create the "otherrel" RelOptInfos of the appendrels as a separate step
of query_planner.  Newly added function add_other_rels_to_query() which
adds "other rel" RelOptInfos of child relations is called after
query_planner has finished distributing restrict clauses to baserels.
Child relations in this case include both those of flattened UNION ALL
subqueries and inheritance child tables.  Child RangeTblEntrys and
AppendRelInfos are added early in subquery_planner for both types of child
relations.  Of the two, we'd like to delay adding the inheritance
children, which is done in the next patch.

See patch 0001.

2. Defer inheritance expansion to add_other_rels_to_query().  Although the
purpose of doing this is to perform partition pruning before adding the
children, this patch doesn't change when the pruning occurs.  It deals
with other issues that must be taken care of due to adding children during
query_planner instead of during subquery_planner.  Especially,
inheritance_planner now has to add the child target relations on its own.
Also, delaying adding children also affects adding junk columns to the
query's targetlist based on PlanRowMarks, because preprocess_targetlist
can no longer finalize which junk columns to add for a "parent"
PlanRowMark; that must be delayed until all child PlanRowMarks are added
and their allMarkTypes propagated to the parent PlanRowMark.

See patch 0002.

3. Because inheritance_planner calls query_planner separately for each
target child relation and the patch 0002 above puts query_planner in
charge of inheritance expansion, that means child tables of source
inheritance sets will be added as many times as there are target children.
 This makes update/delete queries containing inherited source tables
somewhat slow.  This patch adjusts inheritance_planner to reuse source
inheritance children added during the planning of 1st child query for the
planning of subsequent child queries.

See patch 0003.

4. Now that all the issues arising due to late inheritance expansion have
been taken care of, this patch moves where partition pruning occurs.
Today it's in set_append_rel_size() and this patch moves it to
expand_partitioned_rtentry() where partitions are added.  Only the
partitions remaining after pruning are added, so some members of part_rels
can remain NULL.  Some of the places that access partition RelOptInfos
using that array needed to be made aware of that.

See patch 0004.

5. There are a few places which loop over *all* members of part_rels array
of a partitioned parent's RelOptInfo to do something with the partition
rels.   Some of those loops run even for point-lookup queries where only
one partition would be accessed, which is inefficient.  This patch adds a
Bitmapset member named 'live_parts' to RelOptInfo, whose value is the set
of indexes of unpruned partitions in the parent's RelOptInfo.  The
aforementioned loops are now replaced by looping over live_parts Bitmapset
instead.

See patch 0005.

6. set_relation_partition_info today copies the PartitionBoundInfo from
the relcache using partition_bounds_copy.  Doing partition_bounds_copy
gets expensive as the number of partitions increases and it really doesn't
seem necessary for the planner to create its own copy.  This patch removes
the partition_bounds_copy() and simply uses the relcache pointer.

See patch 0006.

Hope that the above division makes the changes easier to review.

Thanks,
Amit

Attachments: download all (6 files, .tar.gz)

v26-0001-Build-other-rels-of-appendrel-baserels-in-a-sepa.patch (text/plain, 11.4K) download From 1d83debe07040a93721f17785d075d26dab6f088 Mon Sep 17 00:00:00 2001 From: Amit <amitlangote09@gmail.com> Date: Sat, 2 Mar 2019 14:13:13 +0900 Subject: [PATCH v26 1/6] Build "other rels" of appendrel baserels in a separate step add_other_rels_to_query() runs after query_planner has distributed restrict clauses to base relations. This will allow us to use the clauses applied a given partitioned baserel to perform partition pruning, and add other rels for only the unpruned partiitons. --- src/backend/optimizer/path/allpaths.c | 2 +- src/backend/optimizer/plan/initsplan.c | 53 +++++++++++++-- src/backend/optimizer/plan/planmain.c | 15 +++-- src/backend/optimizer/util/relnode.c | 118 +++++++++++++++++++++------------ src/include/optimizer/pathnode.h | 2 + src/include/optimizer/planmain.h | 1 + 6 files changed, 140 insertions(+), 51 deletions(-) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 0debac75c6..8d8a8f17d5 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -1028,7 +1028,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, /* * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * add_other_rels_to_query. */ childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 2afc3f1dfe..54fc4dda97 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -20,6 +20,7 @@ #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" +#include "optimizer/inherit.h" #include "optimizer/joininfo.h" #include "optimizer/optimizer.h" #include "optimizer/pathnode.h" @@ -30,6 +31,7 @@ #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" #include "parser/analyze.h" +#include "parser/parsetree.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -97,10 +99,11 @@ static void check_hashjoinable(RestrictInfo *restrictinfo); * jtnode. Internally, the function recurses through the jointree. * * At the end of this process, there should be one baserel RelOptInfo for - * every non-join RTE that is used in the query. Therefore, this routine - * is the only place that should call build_simple_rel with reloptkind - * RELOPT_BASEREL. (Note: build_simple_rel recurses internally to build - * "other rel" RelOptInfos for the members of any appendrels we find here.) + * every non-join RTE that is specified in the query. Therefore, this + * routine is the only place that should call build_simple_rel with + * reloptkind RELOPT_BASEREL. (Note: "other rel" RelOptInfos for the + * members of any appendrels we find here are added built later when + * query_planner calls add_other_rels_to_query().) */ void add_base_rels_to_query(PlannerInfo *root, Node *jtnode) @@ -133,6 +136,48 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode) (int) nodeTag(jtnode)); } +/* + * add_other_rels_to_query + * + * Scan the query's jointree and for each base rels that is an appendrel, + * create otherrel RelOptInfos of its children + * + * At the end of this process, there should be RelOptInfos for all relations + * that will be scanned by the query. + */ +void +add_other_rels_to_query(PlannerInfo *root, Node *jtnode) +{ + if (jtnode == NULL) + return; + if (IsA(jtnode, RangeTblRef)) + { + int varno = ((RangeTblRef *) jtnode)->rtindex; + RangeTblEntry *rte = rt_fetch(varno, root->parse->rtable); + + if (rte->inh) + (void) add_appendrel_other_rels(root, rte, varno); + } + else if (IsA(jtnode, FromExpr)) + { + FromExpr *f = (FromExpr *) jtnode; + ListCell *l; + + foreach(l, f->fromlist) + add_other_rels_to_query(root, lfirst(l)); + } + else if (IsA(jtnode, JoinExpr)) + { + JoinExpr *j = (JoinExpr *) jtnode; + + add_other_rels_to_query(root, j->larg); + add_other_rels_to_query(root, j->rarg); + } + else + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(jtnode)); +} + /***************************************************************************** * diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 3cedd01c98..03c81772a3 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -159,10 +159,8 @@ query_planner(PlannerInfo *root, List *tlist, setup_append_rel_array(root); /* - * Construct RelOptInfo nodes for all base relations in query, and - * indirectly for all appendrel member relations ("other rels"). This - * will give us a RelOptInfo for every "simple" (non-join) rel involved in - * the query. + * Construct RelOptInfo nodes for all base relations directly mentioned + * in query, but not any appendrel member relations ("other rels") yet. * * Note: the reason we find the rels by searching the jointree and * appendrel list, rather than just scanning the rangetable, is that the @@ -204,6 +202,15 @@ query_planner(PlannerInfo *root, List *tlist, generate_base_implied_equalities(root); /* + * Now that we have restrict clauses figured out and assigned to proper + * base rels, we can proceed to add otherrels, that is, UNION ALL child + * tables, inheritance child tables. Having restrict clauses ready helps + * to exclude any children that wouldn't be necessary to scan, based on + * constraint exclusion and partition pruning. + */ + add_other_rels_to_query(root, (Node *) root->parse->jointree); + + /* * We have completed merging equivalence sets, so it's now possible to * generate pathkeys in canonical form; so compute query_pathkeys and * other pathkeys fields in PlannerInfo. diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 4130514952..a6c3eedc56 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -16,19 +16,26 @@ #include <limits.h> +#include "access/table.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" +#include "optimizer/inherit.h" +#include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/placeholder.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" #include "partitioning/partbounds.h" +#include "partitioning/partdesc.h" #include "utils/hsearch.h" +#include "utils/rel.h" typedef struct JoinHashEntry @@ -273,53 +280,80 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); + return rel; +} + +/* + * add_appendrel_other_rels + * This adds the "other rel" RelOptInfos a given "appendrel" baserel + * + * Parent relation in this case is the parent subquery in the flattened UNION + * ALL case or an inheritance parent table. + */ +void +add_appendrel_other_rels(PlannerInfo *root, RangeTblEntry *rte, Index rti) +{ + ListCell *l; + RelOptInfo *rel; + Relation relation = NULL; + int i; + + rel = find_base_rel(root, rti); + /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. + * For partitioned tables, we need to store the child RelOptInfos in the + * rel->part_rels array too. */ - if (rte->inh) + if (rel->part_scheme) { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; - - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); - - foreach(l, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; - } - - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); + rel->part_rels = (RelOptInfo **) + palloc0(sizeof(RelOptInfo *) * rel->nparts); + relation = table_open(rte->relid, NoLock); } - return rel; + /* + * We don't need to use expand_planner_arrays in this case, because + * no new child RTEs are created. setup_simple_rel_arrays() and + * setup_append_rel_array would've considered these child RTEs when + * allocating space for various arrays. + */ + i = 0; + foreach(l, root->append_rel_list) + { + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + RangeTblEntry *childrte; + RelOptInfo *childrel; + + if (appinfo->parent_relid != rti) + continue; + + Assert(childRTindex < root->simple_rel_array_size); + childrte = root->simple_rte_array[childRTindex]; + Assert(childrte != NULL); + childrel = build_simple_rel(root, childRTindex, rel); + + /* + * For partitioned parents, we also need to add childrel to its + * part_rels array. The order in which child tables appear in + * append_rel_list is same as the order in which they appear in the + * parent's PartitionDesc, so assigning partitions like this works. + */ + if (rel->part_scheme != NULL) + { + Assert(rel->nparts > 0 && i < rel->nparts); + rel->part_rels[i] = childrel; + } + + i++; + + /* Child may itself be an inherited relation. */ + if (childrte->inh) + add_appendrel_other_rels(root, childrte, childRTindex); + } + + if (relation) + table_close(relation, NoLock); } /* diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 574bb85b50..1a07963a7d 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -279,6 +279,8 @@ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); +extern void add_appendrel_other_rels(PlannerInfo *root, RangeTblEntry *rte, + Index rti); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); extern RelOptInfo *find_join_rel(PlannerInfo *root, Relids relids); extern RelOptInfo *build_join_rel(PlannerInfo *root, diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index 3bbdb5e2f7..035caac500 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -65,6 +65,7 @@ extern int from_collapse_limit; extern int join_collapse_limit; extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode); +extern void add_other_rels_to_query(PlannerInfo *root, Node *jtnode); extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist); extern void add_vars_to_targetlist(PlannerInfo *root, List *vars, Relids where_needed, bool create_new_ph); -- 2.11.0
v26-0002-Delay-adding-inheritance-child-tables-until-quer.patch (text/plain, 32.9K) download From f6d33752dd38c02707da9149f874b348df956ff0 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 4 Mar 2019 15:10:29 +0900 Subject: [PATCH v26 2/6] Delay adding inheritance child tables until query_planner Inheritance child tables are now added by add_other_rels_to_query, which is called by query_planner. This replaces expand_inherited_tables called by subquery_planner as the method of adding child tables. At the point when add_other_rels_to_query is called, restriction clauses have already been assigned to individual base relations, so it will now be possible to perform partition pruning so that we know exact child tables to add in that case. This commit however doesn't change when pruning is performed. Since we're now adding children (and so any PlanRowMarks for them) much later, preprocess_targetlist cannot conclude which junk columns to add for a given "parent" PlanRowMark, because it depends on the value of allMarkTypes field of PlanRowMarks. The correct value of it cannot be determined until after we've seen all the child tables that will be scanned, so we must delay adding junk columns based on "parent" PlanRowMarks too. inheritance_planner now adds the child target tables on its own, because subquery_planner doesn't. Also, since it calls query_planner once for every child relation, source inheritance child tables are added multiple times. This both slows down queries when there are inherited tables in UPDATE/DELETE's FROM clause (source inheritance) and changes the EXPLAIN output a bit because source inheritance child tables' aliases come out different for every targer child subplan. Another patch will fix both these issues. --- contrib/postgres_fdw/expected/postgres_fdw.out | 24 ++--- src/backend/optimizer/plan/planner.c | 54 +++++++--- src/backend/optimizer/prep/preptlist.c | 131 ++++++++++++++++--------- src/backend/optimizer/util/inherit.c | 126 ++++++++++++++++-------- src/backend/optimizer/util/plancat.c | 3 +- src/backend/optimizer/util/relnode.c | 55 +++++++++++ src/include/nodes/pathnodes.h | 7 ++ src/include/optimizer/inherit.h | 4 +- src/include/optimizer/pathnode.h | 1 + src/include/optimizer/prep.h | 2 + src/test/regress/expected/partition_prune.out | 12 +-- src/test/regress/expected/rowsecurity.out | 16 +-- 12 files changed, 298 insertions(+), 137 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 42108bd3d4..4d31cfed5d 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7128,15 +7128,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7166,15 +7166,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7203,15 +7203,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index bc81535905..c71a40e6ba 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -25,6 +25,7 @@ #include "access/table.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -641,6 +642,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->wt_param_id = -1; root->non_recursive_path = NULL; root->partColsUpdated = false; + root->contains_inherit_children = false; /* * If there is a WITH list, process each WITH query and either convert it @@ -714,27 +716,24 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } /* * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). + * pullup (so that all non-inherited RTEs are present). */ preprocess_rowmarks(root); /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain RTE_RELATION entries, so it's OK (and marginally more efficient) - * to do it after checking for joins and other special RTEs. We must do - * this after pulling up subqueries, else we'd fail to handle inherited - * tables in subqueries. - */ - expand_inherited_tables(root); - - /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -1210,10 +1209,31 @@ inheritance_planner(PlannerInfo *root) Query *parent_parse; Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); PlannerInfo **parent_roots = NULL; + List *orig_append_rel_list = list_copy(root->append_rel_list); Assert(parse->commandType != CMD_INSERT); /* + * Add child target relations. Note that this only adds the children of + * the query's target relation and no other. Especially, children of any + * source relations are added when the loop below calls grouping_planner + * on the *1st* child target relation. + */ + parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); + + expand_inherited_rtentry(root, parent_rte, top_parentRTindex); + + /* + * If parent no longer has any children, then treat this as an update of + * a single table. + */ + if (!parent_rte->inh) + { + grouping_planner(root, false, 0.0 /* retrieve all tuples */); + return; + } + + /* * We generate a modified instance of the original Query for each target * relation, plan that, and put all the plans into a list that will be * controlled by a single ModifyTable node. All the instances share the @@ -1274,7 +1294,6 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1325,6 +1344,9 @@ inheritance_planner(PlannerInfo *root) subroot = makeNode(PlannerInfo); memcpy(subroot, parent_root, sizeof(PlannerInfo)); + /* grouping_planner doesn't need to see the target children. */ + subroot->append_rel_list = list_copy(orig_append_rel_list); + /* * Generate modified query with this rel as target. We first apply * adjust_appendrel_attrs, which copies the Query and changes @@ -2618,7 +2640,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2643,7 +2665,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 5392d1a561..ea63b6a496 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -126,61 +126,23 @@ preprocess_targetlist(PlannerInfo *root) foreach(lc, root->rowMarks) { PlanRowMark *rc = (PlanRowMark *) lfirst(lc); - Var *var; - char resname[32]; - TargetEntry *tle; + List *junk_tles; /* child rels use the same junk attrs as their parents */ if (rc->rti != rc->prti) continue; - if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) - { - /* Need to fetch TID */ - var = makeVar(rc->rti, - SelfItemPointerAttributeNumber, - TIDOID, - -1, - InvalidOid, - 0); - snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - } - if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) - { - /* Need the whole row as a junk var */ - var = makeWholeRowVar(rt_fetch(rc->rti, range_table), - rc->rti, - 0, - false); - snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - } + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which will change the parent row + * mark's allMarkTypes fields. + */ + if (rc->isParent && !root->contains_inherit_children) + continue; - /* If parent of inheritance tree, always fetch the tableoid too. */ - if (rc->isParent) - { - var = makeVar(rc->rti, - TableOidAttributeNumber, - OIDOID, - -1, - InvalidOid, - 0); - snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - } + junk_tles = get_rowmark_junk_tles(root, tlist, rc); + tlist = list_concat(tlist, junk_tles); } /* @@ -434,3 +396,74 @@ get_plan_rowmark(List *rowmarks, Index rtindex) } return NULL; } + +/* + * get_rowmark_junk_tles + * Returns TLEs for junk columns necessary for implementing given + * PlanRowMark + * + * These TLEs are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark. + */ +List * +get_rowmark_junk_tles(PlannerInfo *root, List *tlist, PlanRowMark *rc) +{ + List *range_table = root->parse->rtable; + int tlist_len = list_length(tlist); + List *junk_tles = NIL; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + tlist_len + 1, + pstrdup(resname), + true); + junk_tles = lappend(junk_tles, tle); + tlist_len++; + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(rt_fetch(rc->rti, range_table), + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + tlist_len + 1, + pstrdup(resname), + true); + junk_tles = lappend(junk_tles, tle); + tlist_len++; + } + + /* For inheritance cases, always fetch the tableoid too. */ + if (rc->isParent) + { + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + tlist_len + 1, + pstrdup(resname), + true); + junk_tles = lappend(junk_tles, tle); + } + + return junk_tles; +} diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index a014a12060..fb2977dfbe 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -21,14 +21,14 @@ #include "miscadmin.h" #include "optimizer/appendinfo.h" #include "optimizer/inherit.h" +#include "optimizer/pathnode.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "partitioning/partdesc.h" #include "utils/rel.h" -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, Index parentRTindex, Relation parentrel, @@ -45,36 +45,6 @@ static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, /* - * expand_inherited_tables - * Expand each rangetable entry that represents an inheritance set - * into an "append relation". At the conclusion of this process, - * the "inh" flag is set in all and only those RTEs that are append - * relation parents. - */ -void -expand_inherited_tables(PlannerInfo *root) -{ - Index nrtes; - Index rti; - ListCell *rl; - - /* - * expand_inherited_rtentry may add RTEs to parse->rtable. The function is - * expected to recursively handle any RTEs that it creates with inh=true. - * So just scan as far as the original end of the rtable list. - */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) - { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); - - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); - } -} - -/* * expand_inherited_rtentry * Check whether a rangetable entry represents an inheritance set. * If so, add entries for all the child tables to the query's @@ -94,7 +64,7 @@ expand_inherited_tables(PlannerInfo *root) * Since a partitioned table is not scanned, it might have only one associated * AppendRelInfo. */ -static void +void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) { Oid parentOID; @@ -103,6 +73,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) LOCKMODE lockmode; List *inhOIDs; ListCell *l; + RelOptInfo *rel = NULL; /* Does RT entry allow inheritance? */ if (!rte->inh) @@ -178,6 +149,17 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } /* + * Expand planner arrays for adding the child relations. Can't do + * this if we're not being called from query_planner. + */ + if (root->simple_rel_array_size > 0) + { + /* Needed when creating child RelOptInfos. */ + rel = find_base_rel(root, rti); + expand_planner_arrays(root, list_length(inhOIDs)); + } + + /* * This table has no partitions. Expand any plain inheritance * children in the order the OIDs were returned by * find_all_inheritors. @@ -210,6 +192,10 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) &appinfos, &childrte, &childRTindex); + /* Create the otherrel RelOptInfo too. */ + if (rel) + (void) build_simple_rel(root, childRTindex, rel); + /* Close child relations, but keep locks */ if (childOID != parentOID) table_close(newrelation, NoLock); @@ -230,6 +216,19 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = root->processed_tlist; + List *junk_tles = get_rowmark_junk_tles(root, tlist, oldrc); + + build_base_rel_tlists(root, junk_tles); + root->processed_tlist = list_concat(root->processed_tlist, junk_tles); + } + table_close(oldrelation, NoLock); } @@ -247,6 +246,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, RangeTblEntry *childrte; Index childRTindex; PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); + RelOptInfo *rel = NULL; check_stack_depth(); @@ -266,6 +266,23 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, root->partColsUpdated = has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + /* + * Expand planner arrays for adding the child relations. Can't do + * this if we're not being called from query_planner. + * + * We'll need nparts + 1 new slots, because we also store the parent as + * a child relation. FIXME: no longer add the parent as child too + */ + if (root->simple_rel_array_size > 0) + { + rel = find_base_rel(root, parentRTindex); + expand_planner_arrays(root, partdesc->nparts + 1); + Assert(rel->part_rels == NULL); + if (partdesc->nparts > 0) + rel->part_rels = (RelOptInfo **) palloc0(rel->nparts * + sizeof(RelOptInfo *)); + } + /* First expand the partitioned table itself. */ expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, top_parentrc, parentrel, @@ -301,8 +318,12 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, parentrel, top_parentrc, childrel, appinfos, &childrte, &childRTindex); + /* Create the otherrel RelOptInfo too. */ + if (rel) + rel->part_rels[i] = build_simple_rel(root, childRTindex, rel); + /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + if (childrte->inh) expand_partitioned_rtentry(root, childrte, childRTindex, childrel, top_parentrc, lockmode, appinfos); @@ -345,7 +366,8 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, Oid childOID = RelationGetRelid(childrel); RangeTblEntry *childrte; Index childRTindex; - AppendRelInfo *appinfo; + AppendRelInfo *appinfo = NULL; + PartitionDesc childrel_partdesc = RelationGetPartitionDesc(childrel); /* * Build an RTE for the child, and attach to query's rangetable list. We @@ -363,9 +385,14 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childrte_p = childrte; childrte->relid = childOID; childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ + /* + * A partitioned child will need to be expanded further, but only if it + * actually has partitions. A partitioned table with zero children is + * specially handled in set_rel_size(). + */ if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) + childrte->relkind == RELKIND_PARTITIONED_TABLE && + childrel_partdesc->nparts > 0) childrte->inh = true; else childrte->inh = false; @@ -376,13 +403,11 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childRTindex_p = childRTindex; /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. + * Don't need an AppendRelInfo for duplicate RTEs we create for + * partitioned table parent rels. */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) + if (childrte->relid != parentOID || + childrte->relkind != RELKIND_PARTITIONED_TABLE) { appinfo = make_append_rel_info(parentrel, childrel, parentRTindex, childRTindex); @@ -409,6 +434,21 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, } /* + * Store the RTE and appinfo in the PlannerInfo, which the caller must + * already have allocated space for. + */ + if (root->simple_rel_array_size > 0) + { + Assert(childRTindex < root->simple_rel_array_size); + if (root->simple_rte_array[childRTindex]) + elog(ERROR, "rel %d already exists", childRTindex); + root->simple_rte_array[childRTindex] = childrte; + if (root->append_rel_array[childRTindex]) + elog(ERROR, "child relation %d already exists", childRTindex); + root->append_rel_array[childRTindex] = appinfo; + } + + /* * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE. */ if (top_parentrc) diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 78a96b4ee2..549af9dc6e 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -2090,7 +2090,8 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); + if (partdesc->nparts > 0) + rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index a6c3eedc56..1dbf19e71b 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -139,6 +139,50 @@ setup_append_rel_array(PlannerInfo *root) } /* + * expand_planner_arrays + * Expand the PlannerInfo arrays by add_size members and initialize the + * the newly added bytes with zero + */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size; + + Assert(add_size > 0); + + new_size = root->simple_rel_array_size + add_size; + + /* Expand various arrays and 0-initialize added bytes. */ + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + + if (root->append_rel_array) + { + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + } + else + { + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + } + + root->simple_rel_array_size = new_size; +} + +/* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. */ @@ -298,6 +342,17 @@ add_appendrel_other_rels(PlannerInfo *root, RangeTblEntry *rte, Index rti) Relation relation = NULL; int i; + /* + * Add inheritance children to the query if not already done. For child + * tables that are themselves partitioned, their children will be added + * recursively. + */ + if (rte->rtekind == RTE_RELATION && !root->contains_inherit_children) + { + expand_inherited_rtentry(root, rte, rti); + return; + } + rel = find_base_rel(root, rti); /* diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index a008ae07da..c2e292e54c 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -348,6 +348,13 @@ struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* + * Does this PlannerInfo and its Query object contain *all* inheritance + * children? If true, the RTEs, the AppendRelInfos, and the PlanRowMarks + * of all the children are assumed to be present. + */ + bool contains_inherit_children; }; diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h index d2418f15cf..427c1d0abe 100644 --- a/src/include/optimizer/inherit.h +++ b/src/include/optimizer/inherit.h @@ -16,7 +16,7 @@ #include "nodes/pathnodes.h" - -extern void expand_inherited_tables(PlannerInfo *root); +extern void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, + Index rti); #endif /* INHERIT_H */ diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 1a07963a7d..60361507d2 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -277,6 +277,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern void add_appendrel_other_rels(PlannerInfo *root, RangeTblEntry *rte, diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index a9b2c9026c..5979c9885e 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -37,6 +37,8 @@ extern Relids get_relids_for_join(Query *query, int joinrelid); extern List *preprocess_targetlist(PlannerInfo *root); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); +extern List *get_rowmark_junk_tles(PlannerInfo *root, List *tlist, + PlanRowMark *rc); /* * prototypes for prepunion.c diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index 30946f77b6..71942394ba 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -2559,16 +2559,16 @@ update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a; Index Cond: (a = 1) -> Nested Loop (actual rows=1 loops=1) -> Append (actual rows=1 loops=1) - -> Bitmap Heap Scan on ab_a1_b1 ab_a1_b1_1 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b1 ab_a1_b1_2 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b2 ab_a1_b2_1 (actual rows=1 loops=1) + -> Bitmap Heap Scan on ab_a1_b2 ab_a1_b2_2 (actual rows=1 loops=1) Recheck Cond: (a = 1) Heap Blocks: exact=1 -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_1 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_2 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) @@ -2580,16 +2580,16 @@ update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a; Index Cond: (a = 1) -> Nested Loop (actual rows=0 loops=1) -> Append (actual rows=1 loops=1) - -> Bitmap Heap Scan on ab_a1_b1 ab_a1_b1_1 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b1 ab_a1_b1_3 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b2 ab_a1_b2_1 (actual rows=1 loops=1) + -> Bitmap Heap Scan on ab_a1_b2 ab_a1_b2_3 (actual rows=1 loops=1) Recheck Cond: (a = 1) Heap Blocks: exact=1 -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_1 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_3 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out index 2e170497c9..a6a499ed4a 100644 --- a/src/test/regress/expected/rowsecurity.out +++ b/src/test/regress/expected/rowsecurity.out @@ -1815,26 +1815,26 @@ AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2; -> Seq Scan on t3 t1_2_2 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Nested Loop - Join Filter: (t1_1_1.b = t1_2.b) + Join Filter: (t1_1_1.b = t1_2_3.b) -> Seq Scan on t2 t1_1_1 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Append - -> Seq Scan on t1 t1_2 + -> Seq Scan on t1 t1_2_3 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t2 t1_2_1 + -> Seq Scan on t2 t1_2_4 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t3 t1_2_2 + -> Seq Scan on t3 t1_2_5 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Nested Loop - Join Filter: (t1_1_2.b = t1_2.b) + Join Filter: (t1_1_2.b = t1_2_6.b) -> Seq Scan on t3 t1_1_2 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Append - -> Seq Scan on t1 t1_2 + -> Seq Scan on t1 t1_2_6 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t2 t1_2_1 + -> Seq Scan on t2 t1_2_7 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t3 t1_2_2 + -> Seq Scan on t3 t1_2_8 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) (37 rows) -- 2.11.0
v26-0003-Adjust-inheritance_planner-to-reuse-source-child.patch (text/plain, 17.3K) download From 7d8c20357ecd031bda163b830d66141f2c3e7346 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 4 Mar 2019 15:52:11 +0900 Subject: [PATCH v26 3/6] Adjust inheritance_planner to reuse source child tables Set of source inheritance child tables won't change across repeated planning of the query for different target child relations. So, note down the RangeTblEntrys, AppendRelInfos, and PlanRowMarks of the source inheritance child tables after the planning for the first child table is finished. When planning for the subsequent child target tables, put the saved objects of source inheritance child tables into the child PlannerInfos and set contains_inherit_children to true, so that query_planner doesn't add them again. This restores the regression test outputs to their original state, because like before, source inheritance child tables are not added multiple times. --- src/backend/optimizer/plan/planner.c | 216 +++++++++++++++++--------- src/test/regress/expected/partition_prune.out | 12 +- src/test/regress/expected/rowsecurity.out | 16 +- 3 files changed, 157 insertions(+), 87 deletions(-) diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index c71a40e6ba..94557108e8 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -1206,10 +1206,15 @@ inheritance_planner(PlannerInfo *root) Index rti; RangeTblEntry *parent_rte; PlannerInfo *parent_root; - Query *parent_parse; Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); PlannerInfo **parent_roots = NULL; List *orig_append_rel_list = list_copy(root->append_rel_list); + List *orig_row_marks = list_copy(root->rowMarks); + List *orig_rtable = list_copy(root->parse->rtable); + List *rtable_with_target; + List *source_appinfos = NIL; + List *source_child_rowmarks = NIL; + List *source_child_rtes = NIL; Assert(parse->commandType != CMD_INSERT); @@ -1233,6 +1238,13 @@ inheritance_planner(PlannerInfo *root) return; } + + /* + * This one also contains the child target relations, but no other + * child relations. + */ + rtable_with_target = list_copy(root->parse->rtable); + /* * We generate a modified instance of the original Query for each target * relation, plan that, and put all the plans into a list that will be @@ -1249,10 +1261,13 @@ inheritance_planner(PlannerInfo *root) * management of the rowMarks list. * * To begin with, generate a bitmapset of the relids of the subquery RTEs. + * We use orig_rtable, not rtable_with_target (parse->rtable), because we + * wouldn't need to consider any newly added RTEs as they all must be + * RTE_RELATION entries. */ subqueryRTindexes = NULL; rti = 1; - foreach(lc, parse->rtable) + foreach(lc, orig_rtable) { RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); @@ -1265,26 +1280,24 @@ inheritance_planner(PlannerInfo *root) * Next, we want to identify which AppendRelInfo items contain references * to any of the aforesaid subquery RTEs. These items will need to be * copied and modified to adjust their subquery references; whereas the - * other ones need not be touched. It's worth being tense over this - * because we can usually avoid processing most of the AppendRelInfo - * items, thereby saving O(N^2) space and time when the target is a large - * inheritance tree. We can identify AppendRelInfo items by their - * child_relid, since that should be unique within the list. + * other ones need not be touched. We can assume that all (if any) + * entries in orig_append_rel_list contain references to subquery RTEs, + * because they correspond to flattneed UNION ALL subqueries. Especially, + * we don't need to bother with those added when adding the child target + * relations. We can identify AppendRelInfo items by their child_relid, + * since that should be unique within the list. */ modifiableARIindexes = NULL; - if (subqueryRTindexes != NULL) + foreach(lc, orig_append_rel_list) { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); + AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) || - bms_is_member(appinfo->child_relid, subqueryRTindexes) || - bms_overlap(pull_varnos((Node *) appinfo->translated_vars), - subqueryRTindexes)) - modifiableARIindexes = bms_add_member(modifiableARIindexes, - appinfo->child_relid); - } + Assert(bms_is_member(appinfo->parent_relid, subqueryRTindexes) || + bms_is_member(appinfo->child_relid, subqueryRTindexes) || + bms_overlap(pull_varnos((Node *) appinfo->translated_vars), + subqueryRTindexes)); + modifiableARIindexes = bms_add_member(modifiableARIindexes, + appinfo->child_relid); } /* @@ -1335,7 +1348,6 @@ inheritance_planner(PlannerInfo *root) */ parent_root = parent_roots[appinfo->parent_relid]; Assert(parent_root != NULL); - parent_parse = parent_root->parse; /* * We need a working copy of the PlannerInfo so that we can control @@ -1344,8 +1356,51 @@ inheritance_planner(PlannerInfo *root) subroot = makeNode(PlannerInfo); memcpy(subroot, parent_root, sizeof(PlannerInfo)); - /* grouping_planner doesn't need to see the target children. */ - subroot->append_rel_list = list_copy(orig_append_rel_list); + /* + * Per the above comment, we'll be changing only the AppendRelInfos + * that are contained in orig_append_rel_list, so only copy those. + */ + subroot->append_rel_list = copyObject(orig_append_rel_list); + + /* + * Likewise, for PlanRowMarks. (Fortunately, the executor doesn't + * need to see the modified copies --- we can just pass it the + * original rowMarks list.) + */ + subroot->rowMarks = copyObject(orig_row_marks); + + /* + * No need to copy of the RTEs themselves, but do copy the List + * structure. + */ + subroot->parse->rtable = list_copy(rtable_with_target); + + /* + * If this isn't the first child query, then we can use the child + * objects for source child relations created during the planning of + * 1st child query. IOW, this planning run doesn't need to create the + * child objects again, indicated by setting contains_inherit_children + * sub-PlannerInfo. + */ + if (source_appinfos) + { + subroot->append_rel_list = list_concat(subroot->append_rel_list, + source_appinfos); + /* + * XXX Assert(source_child_rowmarks != NIL); + */ + subroot->rowMarks = list_concat(subroot->rowMarks, + source_child_rowmarks); + Assert(source_child_rtes != NIL); + subroot->parse->rtable = list_concat(subroot->parse->rtable, + source_child_rtes); + + /* + * We have the children, so no need to add them again during this + * planning cycle. + */ + subroot->contains_inherit_children = true; + } /* * Generate modified query with this rel as target. We first apply @@ -1355,7 +1410,7 @@ inheritance_planner(PlannerInfo *root) */ subroot->parse = (Query *) adjust_appendrel_attrs(parent_root, - (Node *) parent_parse, + (Node *) subroot->parse, 1, &appinfo); /* @@ -1421,41 +1476,6 @@ inheritance_planner(PlannerInfo *root) nominalRelation = appinfo->child_relid; /* - * The rowMarks list might contain references to subquery RTEs, so - * make a copy that we can apply ChangeVarNodes to. (Fortunately, the - * executor doesn't need to see the modified copies --- we can just - * pass it the original rowMarks list.) - */ - subroot->rowMarks = copyObject(parent_root->rowMarks); - - /* - * The append_rel_list likewise might contain references to subquery - * RTEs (if any subqueries were flattenable UNION ALLs). So prepare - * to apply ChangeVarNodes to that, too. As explained above, we only - * want to copy items that actually contain such references; the rest - * can just get linked into the subroot's append_rel_list. - * - * If we know there are no such references, we can just use the outer - * append_rel_list unmodified. - */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - subroot->append_rel_list = NIL; - foreach(lc2, parent_root->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, modifiableARIindexes)) - appinfo2 = copyObject(appinfo2); - - subroot->append_rel_list = lappend(subroot->append_rel_list, - appinfo2); - } - } - - /* * Add placeholders to the child Query's rangetable list to fill the * RT indexes already reserved for subqueries in previous children. * These won't be referenced, so there's no need to make them very @@ -1468,23 +1488,23 @@ inheritance_planner(PlannerInfo *root) /* * If this isn't the first child Query, generate duplicates of all * subquery RTEs, and adjust Var numbering to reference the - * duplicates. To simplify the loop logic, we scan the original rtable - * not the copy just made by adjust_appendrel_attrs; that should be OK - * since subquery RTEs couldn't contain any references to the target - * rel. + * duplicates. Note that we scan the original rtable before any + * child target relations were added, which is OK, because no other + * RTEs would contain references to subquery rels being modified. */ if (final_rtable != NIL && subqueryRTindexes != NULL) { ListCell *lr; rti = 1; - foreach(lr, parent_parse->rtable) + foreach(lr, orig_rtable) { RangeTblEntry *rte = lfirst_node(RangeTblEntry, lr); if (bms_is_member(rti, subqueryRTindexes)) { Index newrti; + ListCell *lc2; /* * The RTE can't contain any references to its own RT @@ -1495,19 +1515,21 @@ inheritance_planner(PlannerInfo *root) newrti = list_length(subroot->parse->rtable) + 1; ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); ChangeVarNodes((Node *) subroot->rowMarks, rti, newrti, 0); - /* Skip processing unchanging parts of append_rel_list */ - if (modifiableARIindexes != NULL) + + /* + * UNION ALL related appinfos are the beginning of the + * list, only change those. + */ + foreach(lc2, subroot->append_rel_list) { - ListCell *lc2; + AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, + lc2); - foreach(lc2, subroot->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, - modifiableARIindexes)) - ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); - } + if (bms_is_member(appinfo2->child_relid, + modifiableARIindexes)) + ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); + else + break; } rte = copyObject(rte); ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); @@ -1536,6 +1558,54 @@ inheritance_planner(PlannerInfo *root) subpath = sub_final_rel->cheapest_total_path; /* + * If we finished planning our first child query, copy the source + * child objects that were added during its planning. + */ + if (source_appinfos == NIL && subroot->append_rel_list) + { + int num_skip_appinfos = list_length(orig_append_rel_list); + int num_skip_rowmarks = list_length(orig_row_marks); + int num_skip_rtes = list_length(rtable_with_target); + ListCell *lc2; + + source_appinfos = list_copy_tail(subroot->append_rel_list, + num_skip_appinfos); + Assert(source_child_rowmarks == NIL); + source_child_rowmarks = list_copy_tail(subroot->rowMarks, + num_skip_rowmarks); + Assert(source_child_rtes == NIL); + source_child_rtes = list_copy_tail(subroot->parse->rtable, + num_skip_rtes); + + /* + * Original parent PlanRowMark is modified when adding the + * child PlanRowMarks. Copy those changes so that the planning + * of subsequent child queries works correctly. That is + * necessary, because we won't be adding child objects again, + * so there won't be an opportunity to modify the parent + * PlanRowMark as desired. + * + * All the original parent row marks are the beginning of + * subroot->rowMarks, skip the rest. + */ + foreach(lc2, orig_row_marks) + { + PlanRowMark *oldrc = lfirst_node(PlanRowMark, lc2); + ListCell *lc3; + + foreach(lc3, subroot->rowMarks) + { + PlanRowMark *newrc = lfirst_node(PlanRowMark, lc3); + + if (oldrc->rti == newrc->rti) + oldrc->allMarkTypes = newrc->allMarkTypes; + else + break; + } + } + } + + /* * If this child rel was excluded by constraint exclusion, exclude it * from the result plan. */ diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index 71942394ba..30946f77b6 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -2559,16 +2559,16 @@ update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a; Index Cond: (a = 1) -> Nested Loop (actual rows=1 loops=1) -> Append (actual rows=1 loops=1) - -> Bitmap Heap Scan on ab_a1_b1 ab_a1_b1_2 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b1 ab_a1_b1_1 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b2 ab_a1_b2_2 (actual rows=1 loops=1) + -> Bitmap Heap Scan on ab_a1_b2 ab_a1_b2_1 (actual rows=1 loops=1) Recheck Cond: (a = 1) Heap Blocks: exact=1 -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_2 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_1 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) @@ -2580,16 +2580,16 @@ update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a; Index Cond: (a = 1) -> Nested Loop (actual rows=0 loops=1) -> Append (actual rows=1 loops=1) - -> Bitmap Heap Scan on ab_a1_b1 ab_a1_b1_3 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b1 ab_a1_b1_1 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b2 ab_a1_b2_3 (actual rows=1 loops=1) + -> Bitmap Heap Scan on ab_a1_b2 ab_a1_b2_1 (actual rows=1 loops=1) Recheck Cond: (a = 1) Heap Blocks: exact=1 -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_3 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_1 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out index a6a499ed4a..2e170497c9 100644 --- a/src/test/regress/expected/rowsecurity.out +++ b/src/test/regress/expected/rowsecurity.out @@ -1815,26 +1815,26 @@ AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2; -> Seq Scan on t3 t1_2_2 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Nested Loop - Join Filter: (t1_1_1.b = t1_2_3.b) + Join Filter: (t1_1_1.b = t1_2.b) -> Seq Scan on t2 t1_1_1 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Append - -> Seq Scan on t1 t1_2_3 + -> Seq Scan on t1 t1_2 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t2 t1_2_4 + -> Seq Scan on t2 t1_2_1 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t3 t1_2_5 + -> Seq Scan on t3 t1_2_2 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Nested Loop - Join Filter: (t1_1_2.b = t1_2_6.b) + Join Filter: (t1_1_2.b = t1_2.b) -> Seq Scan on t3 t1_1_2 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Append - -> Seq Scan on t1 t1_2_6 + -> Seq Scan on t1 t1_2 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t2 t1_2_7 + -> Seq Scan on t2 t1_2_1 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t3 t1_2_8 + -> Seq Scan on t3 t1_2_2 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) (37 rows) -- 2.11.0
v26-0004-Perform-pruning-in-expand_partitioned_rtentry.patch (text/plain, 27.4K) download From d3fcb2331bfa8101195134f93aaf7e31010d3361 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 4 Mar 2019 11:43:38 +0900 Subject: [PATCH v26 4/6] Perform pruning in expand_partitioned_rtentry This allows to avoid opening/locking partitions that won't be scanned at all. Since expand_partitioned_rtentry recursivly processes sub-partitioned tables, translated quals must be added right when the child RelOptInfo is built for the sub-partitioned tables. So, build_simple_rel itself performs apply_child_basequals. Code for partitioning optimizations that access partition RelOptInfos from part_rels array of the parent's RelOptInfo must now be made aware that some entries might be NULL due partition pruning. In the case of partitionwise join, even a pruned partition may need to be joined if it falls on the nullable side of an outer join. A dummy RelOptInfo containing a dummy path is built in that case and put into its parent's part_rels array. --- src/backend/optimizer/path/allpaths.c | 176 +--------------------------------- src/backend/optimizer/path/joinrels.c | 92 +++++++++++++++++- src/backend/optimizer/plan/planner.c | 8 ++ src/backend/optimizer/util/inherit.c | 11 ++- src/backend/optimizer/util/relnode.c | 174 ++++++++++++++++++++++++++++++++- src/backend/partitioning/partprune.c | 43 +++++---- 6 files changed, 304 insertions(+), 200 deletions(-) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 8d8a8f17d5..3191836c19 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -138,9 +138,6 @@ static void subquery_push_qual(Query *subquery, static void recurse_push_qual(Node *setOp, Query *topquery, RangeTblEntry *rte, Index rti, Node *qual); static void remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel); -static bool apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, - RelOptInfo *childrel, - RangeTblEntry *childRTE, AppendRelInfo *appinfo); /* @@ -945,8 +942,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -965,21 +960,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, rel->partitioned_child_rels = list_make1_int(rti); /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - - /* * If this is a partitioned baserel, set the consider_partitionwise_join * flag; currently, we only consider partitionwise joins with the baserel * if its targetlist doesn't contain a whole-row Var. @@ -1033,30 +1013,11 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); + /* build_simple_rel may have already proven the child to be dummy. */ + if (IS_DUMMY_REL(childrel)) continue; - } - - /* - * 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 - * baserestrictinfo quals. - */ - if (!apply_child_basequals(root, rel, childrel, childRTE, appinfo)) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - continue; - } + /* Apply constraint exclusion. */ if (relation_excluded_by_constraints(root, childrel, childRTE)) { /* @@ -1068,7 +1029,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, } /* - * CE failed, so finish copying/modifying targetlist and join quals. + * Constraint exclusion failed, so finish copying/modifying targetlist + * and join quals. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -3557,134 +3519,6 @@ generate_partitionwise_join_paths(PlannerInfo *root, RelOptInfo *rel) list_free(live_children); } -/* - * apply_child_basequals - * Populate childrel's quals based on rel's quals, translating them using - * appinfo. - * - * If any of the resulting clauses evaluate to false or NULL, we return false - * and don't apply any quals. Caller can mark the relation as a dummy rel in - * this case, since it needn't be scanned. - * - * If any resulting clauses evaluate to true, they're unnecessary and we don't - * apply then. - */ -static bool -apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, - RelOptInfo *childrel, RangeTblEntry *childRTE, - AppendRelInfo *appinfo) -{ - List *childquals; - Index cq_min_security; - ListCell *lc; - - /* - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities for - * const-simplification, and perhaps even pseudoconstant quals. Therefore, - * transform each RestrictInfo separately to see if it reduces to a - * constant or pseudoconstant. (We must process them separately to keep - * track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - return false; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. (Currently - * this can only happen in appendrels originating from UNION ALL; - * inheritance child tables don't have their own securityQuals, see - * expand_inherited_rtentry().) Pull any such securityQuals up into the - * baserestrictinfo for the child. This is similar to - * process_security_barrier_quals() for the parent rel, except that we - * can't make any general deductions from such quals, since they don't - * hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - return true; -} - /***************************************************************************** * DEBUG SUPPORT *****************************************************************************/ diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index dfbbfdac6d..66a5cf3334 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -14,6 +14,7 @@ */ #include "postgres.h" +#include "access/table.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" @@ -21,6 +22,8 @@ #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" +#include "optimizer/tlist.h" +#include "parser/parsetree.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -52,6 +55,9 @@ static SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, Relids left_relids, Relids right_relids); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, Relation parentrel, + int partidx); /* @@ -1318,6 +1324,8 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, RelOptInfo *joinrel, SpecialJoinInfo *parent_sjinfo, List *parent_restrictlist) { + Relation baserel1 = NULL, + baserel2 = NULL; bool rel1_is_simple = IS_SIMPLE_REL(rel1); bool rel2_is_simple = IS_SIMPLE_REL(rel2); int nparts; @@ -1368,6 +1376,18 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, nparts = joinrel->nparts; + if (rel1_is_simple) + { + RangeTblEntry *rte = planner_rt_fetch(rel1->relid, root); + + baserel1 = table_open(rte->relid, NoLock); + } + if (rel2_is_simple) + { + RangeTblEntry *rte = planner_rt_fetch(rel2->relid, root); + + baserel2 = table_open(rte->relid, NoLock); + } /* * Create child-join relations for this partitioned join, if those don't * exist. Add paths to child-joins for a pair of child relations @@ -1384,6 +1404,13 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (rel1_is_simple && child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, baserel1, + cnt_parts); + if (rel2_is_simple && child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, baserel2, + cnt_parts); + /* * If a child table has consider_partitionwise_join=false, it means * that it's a dummy relation for which we skipped setting up tlist @@ -1444,6 +1471,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, child_joinrel, child_sjinfo, child_restrictlist); } + + if (baserel1) + table_close(baserel1, NoLock); + if (baserel2) + table_close(baserel2, NoLock); } /* @@ -1462,8 +1494,14 @@ update_child_rel_info(PlannerInfo *root, (Node *) rel->reltarget->exprs, 1, &appinfo); - /* Make child entries in the EquivalenceClass as well */ - if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) + /* + * Make child entries in the EquivalenceClass as well. If the childrel + * appears to be a dummy one (one built by build_dummy_partition_rel()), + * no need to make any new entries, because anything that'd need those + * can instead use the parent's (rel). + */ + if (childrel->relid != rel->relid && + (rel->has_eclass_joins || has_useful_pathkeys(root, rel))) add_child_rel_equivalences(root, appinfo, rel, childrel); childrel->has_eclass_joins = rel->has_eclass_joins; } @@ -1674,3 +1712,53 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +static RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, + Relation parentrel, int partidx) +{ + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + AppendRelInfo *appinfo = make_append_rel_info(parentrel, parentrel, + parent->relid, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 94557108e8..7fd1366652 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -7138,6 +7138,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -7239,6 +7243,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index fb2977dfbe..e2c1f341ce 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -26,6 +26,7 @@ #include "optimizer/planner.h" #include "optimizer/prep.h" #include "partitioning/partdesc.h" +#include "partitioning/partprune.h" #include "utils/rel.h" @@ -242,6 +243,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, PlanRowMark *top_parentrc, LOCKMODE lockmode, List **appinfos) { + Bitmapset *live_parts = 0; int i; RangeTblEntry *childrte; Index childRTindex; @@ -276,12 +278,16 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, if (root->simple_rel_array_size > 0) { rel = find_base_rel(root, parentRTindex); - expand_planner_arrays(root, partdesc->nparts + 1); + if (partdesc->nparts > 0) + live_parts = prune_append_rel_partitions(rel); + expand_planner_arrays(root, bms_num_members(live_parts) + 1); Assert(rel->part_rels == NULL); if (partdesc->nparts > 0) rel->part_rels = (RelOptInfo **) palloc0(rel->nparts * sizeof(RelOptInfo *)); } + else + live_parts = bms_add_range(NULL, 0, partdesc->nparts - 1); /* First expand the partitioned table itself. */ expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, @@ -298,7 +304,8 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, return; } - for (i = 0; i < partdesc->nparts; i++) + i = -1; + while ((i = bms_next_member(live_parts, i)) >= 0) { Oid childOID = partdesc->oids[i]; Relation childrel; diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 1dbf19e71b..adc06cf69a 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -70,6 +70,9 @@ static void build_child_join_reltarget(PlannerInfo *root, RelOptInfo *childrel, int nappinfos, AppendRelInfo **appinfos); +static bool apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *childrel, + RangeTblEntry *childRTE, AppendRelInfo *appinfo); /* @@ -324,10 +327,162 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); + /* + * Copy the parent's quals to the child, with appropriate substitution of + * variables. If any constant false or NULL clauses turn up, we can + * disregard the child as dummy right away. + * + * We must copy the quals now, because if the child rel happens to be a + * partitioned table, expand_partitioned_rtentry (our caller) can + * immediate perform partition prunning using the translated quals. + */ + if (parent) + { + AppendRelInfo *appinfo = root->append_rel_array[relid]; + + Assert(appinfo != NULL); + if (!apply_child_basequals(root, parent, rel, rte, appinfo)) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(rel); + } + } + return rel; } /* + * apply_child_basequals + * Populate childrel's quals based on rel's quals, translating them using + * appinfo. + * + * If any of the resulting clauses evaluate to false or NULL, we return false + * and don't apply any quals. Caller can mark the relation as a dummy rel in + * this case, since it needn't be scanned. + * + * If any resulting clauses evaluate to true, they're unnecessary and we don't + * apply then. + */ +static bool +apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *childrel, RangeTblEntry *childRTE, + AppendRelInfo *appinfo) +{ + List *childquals; + Index cq_min_security; + ListCell *lc; + + /* + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities for + * const-simplification, and perhaps even pseudoconstant quals. Therefore, + * transform each RestrictInfo separately to see if it reduces to a + * constant or pseudoconstant. (We must process them separately to keep + * track of the security level of each qual.) + */ + childquals = NIL; + cq_min_security = UINT_MAX; + foreach(lc, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, + (Node *) rinfo->clause, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + return false; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + /* track minimum security level among child quals */ + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might have + * securityQuals associated with this particular child node. (Currently + * this can only happen in appendrels originating from UNION ALL; + * inheritance child tables don't have their own securityQuals, see + * expand_inherited_rtentry().) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except that we + * can't make any general deductions from such quals, since they don't + * hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* not likely that we'd see constants here, so no check */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, false, false, + security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* + * OK, we've got all the baserestrictinfo quals for this child. + */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + return true; +} + +/* * add_appendrel_other_rels * This adds the "other rel" RelOptInfos a given "appendrel" baserel * @@ -340,6 +495,7 @@ add_appendrel_other_rels(PlannerInfo *root, RangeTblEntry *rte, Index rti) ListCell *l; RelOptInfo *rel; Relation relation = NULL; + PartitionDesc partdesc = NULL; int i; /* @@ -364,6 +520,7 @@ add_appendrel_other_rels(PlannerInfo *root, RangeTblEntry *rte, Index rti) rel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * rel->nparts); relation = table_open(rte->relid, NoLock); + partdesc = RelationGetPartitionDesc(relation); } /* @@ -392,16 +549,23 @@ add_appendrel_other_rels(PlannerInfo *root, RangeTblEntry *rte, Index rti) * For partitioned parents, we also need to add childrel to its * part_rels array. The order in which child tables appear in * append_rel_list is same as the order in which they appear in the - * parent's PartitionDesc, so assigning partitions like this works. + * parent's PartitionDesc. But considering that some partitions may + * have been pruned, we need to look up the correct position to + * assign this childrel by scanning the PartitionDesc. */ if (rel->part_scheme != NULL) { - Assert(rel->nparts > 0 && i < rel->nparts); - rel->part_rels[i] = childrel; + Assert(partdesc != NULL); + for (i = 0; i < partdesc->nparts; i++) + { + if (childrte->relid == partdesc->oids[i]) + { + rel->part_rels[i] = childrel; + break; + } + } } - i++; - /* Child may itself be an inherited relation. */ if (childrte->inh) add_appendrel_other_rels(root, childrte, childRTindex); diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 8c9721935d..30ebce4e40 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -45,6 +45,7 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/cost.h" #include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "partitioning/partprune.h" @@ -433,17 +434,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -537,23 +544,20 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ @@ -561,6 +565,13 @@ prune_append_rel_partitions(RelOptInfo *rel) return NULL; /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. */ @@ -587,15 +598,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* -- 2.11.0
v26-0005-Teach-planner-to-only-process-unpruned-partition.patch (text/plain, 7.4K) download From 5ba60de97462198a698e427de88893734b195538 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 4 Mar 2019 11:57:04 +0900 Subject: [PATCH v26 5/6] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. Partitionwise join still has to visit each member of part_rels, because pruned children may fall on the nullable side of an outer join and an empty Result path must be present for building the outer join paths. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/inherit.c | 1 + src/backend/optimizer/util/relnode.c | 4 ++++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/pathnodes.h | 4 ++++ 6 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 66a5cf3334..57158d90ec 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1470,6 +1470,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } if (baserel1) diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 7fd1366652..4b6d5b87e5 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -7130,7 +7130,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -7138,9 +7140,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -7221,7 +7221,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -7233,7 +7232,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -7243,9 +7244,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index e2c1f341ce..e40ab159a3 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -280,6 +280,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, rel = find_base_rel(root, parentRTindex); if (partdesc->nparts > 0) live_parts = prune_append_rel_partitions(rel); + rel->live_parts = live_parts; expand_planner_arrays(root, bms_num_members(live_parts) + 1); Assert(rel->part_rels == NULL); if (partdesc->nparts > 0) diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index adc06cf69a..63a6ec2548 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -561,6 +561,7 @@ add_appendrel_other_rels(PlannerInfo *root, RangeTblEntry *rte, Index rti) if (childrte->relid == partdesc->oids[i]) { rel->part_rels[i] = childrel; + rel->live_parts = bms_add_member(rel->live_parts, i); break; } } @@ -1987,6 +1988,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 30ebce4e40..0f990ef6a2 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -437,29 +437,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index c2e292e54c..53d0485964 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -712,6 +712,10 @@ typedef struct RelOptInfo List *partition_qual; /* partition constraint */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Indexes into part_rels of the non-NULL + * RelOptInfos of unpruned partitions; exists + * to avoid having to iterate over the entire + * part_rels array to filter NULL entries. */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.11.0
v26-0006-Don-t-copy-PartitionBoundInfo-in-set_relation_pa.patch (text/plain, 1.6K) download From 4a6048ced9bd94aa2f0f544737a0d1acc2b7c806 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 4 Mar 2019 16:03:49 +0900 Subject: [PATCH v26 6/6] Don't copy PartitionBoundInfo in set_relation_partition_info As long as we hold a lock on the table, it shouldn't change, so seems pointless to copy, which can get really expensive as the number of partitions grows beyond thousands. --- src/backend/optimizer/util/plancat.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 549af9dc6e..b75b761033 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -2082,16 +2082,18 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); + /* + * Holding onto the relcache pointer should be OK, as it won't change + * under us as long as we're holding the lock on the relation. + */ if (partdesc->nparts > 0) - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); + rel->boundinfo = partdesc->boundinfo; rel->nparts = partdesc->nparts; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-03-04 10:42  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  0 siblings, 0 replies; 265+ messages in thread

From: Amit Langote @ 2019-03-04 10:42 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: 'Amit Langote' <amitlangote09@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Justin Pryzby <pryzby@telsasoft.com>; pgsql-hackers

Imai-san,

Thanks for the review.

On 2019/03/04 18:14, Imai, Yoshikazu wrote:
> I've taken at glance the codes and there are some minor comments about the patch.
> 
> * You changed a usage/arguments of get_relation_info, but why you did it? I made those codes back to the original code and checked it passes make check. So ISTM there are no logical problems with not changing it. Or if you change it, how about also change a usage/arguments of get_relation_info_hook in the same way?
> 
> -get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
> -				  RelOptInfo *rel)
> +get_relation_info(PlannerInfo *root, RangeTblEntry *rte, RelOptInfo *rel)
>  {
> +	bool		inhparent = rte->inh;
> -	relation = table_open(relationObjectId, NoLock);
> +	relation = heap_open(rte->relid, NoLock);
>   ...
>  	if (get_relation_info_hook)
> -		(*get_relation_info_hook) (root, relationObjectId, inhparent, rel);
> +		(*get_relation_info_hook) (root, rte->relid, rte->inh, rel);
> 
> 
> @@ -217,15 +272,13 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
>  	/* Check type of rtable entry */
>  	switch (rte->rtekind)
>  	{
>  		case RTE_RELATION:
>  			/* Table --- retrieve statistics from the system catalogs */
> -			get_relation_info(root, rte->relid, rte->inh, rel);
> +			get_relation_info(root, rte, rel);
> 
> 
> * You moved the codes of initializing of append rel's partitioned_child_rels in set_append_rel_size() to build_simple_rel(), but is it better to do? I also checked the original code passes make check by doing like above.
> 
> @@ -954,32 +948,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
>  	Assert(IS_SIMPLE_REL(rel));
>  
>  	/*
> -	 * Initialize partitioned_child_rels to contain this RT index.
> -	 *
> -	 * Note that during the set_append_rel_pathlist() phase, we will bubble up
> -	 * the indexes of partitioned relations that appear down in the tree, so
> -	 * that when we've created Paths for all the children, the root
> -	 * partitioned table's list will contain all such indexes.
> -	 */
> -	if (rte->relkind == RELKIND_PARTITIONED_TABLE)
> -		rel->partitioned_child_rels = list_make1_int(rti);
> 
> @@ -274,55 +327,287 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
>  										list_length(rte->securityQuals));
>  
>  	/*
> -	 * If this rel is an appendrel parent, recurse to build "other rel"
> -	 * RelOptInfos for its children.  They are "other rels" because they are
> -	 * not in the main join tree, but we will need RelOptInfos to plan access
> -	 * to them.
> +	 * Add the parent to partitioned_child_rels.
> +	 *
> +	 * Note that during the set_append_rel_pathlist() phase, values of the
> +	 * indexes of partitioned relations that appear down in the tree will
> +	 * be bubbled up into root parent's list so that when we've created
> +	 * Paths for all the children, the root table's list will contain all
> +	 * such indexes.
>  	 */
> -	if (rte->inh)
> +	if (rel->part_scheme)
> +		rel->partitioned_child_rels = list_make1_int(relid);

Both of these changes are not present in the latest patches I posted,
where I also got rid of a lot of unnecessary diffs.

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-03-04 16:20  Jesper Pedersen <jesper.pedersen@redhat.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 1 reply; 265+ messages in thread

From: Jesper Pedersen @ 2019-03-04 16:20 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Amit Langote <amitlangote09@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Justin Pryzby <pryzby@telsasoft.com>; pgsql-hackers

Hi Amit,

Passes check-world.

On 3/4/19 5:38 AM, Amit Langote wrote:
> See patch 0001.
> 

+* members of any appendrels we find here are added built later when

s/built//

> See patch 0002.
>

+		grouping_planner(root, false, 0.0 /* retrieve all tuples */);

Move comment out of function call.

+		if (root->simple_rte_array[childRTindex])
+			elog(ERROR, "rel %d already exists", childRTindex);
+		root->simple_rte_array[childRTindex] = childrte;
+		if (root->append_rel_array[childRTindex])
+			elog(ERROR, "child relation %d already exists", childRTindex);
+		root->append_rel_array[childRTindex] = appinfo;


Could the "if"s be made into Assert's instead ?

+ *		the	newly added bytes with zero

Extra spaces

+	if (rte->rtekind == RTE_RELATION &&	!root->contains_inherit_children)

s/TAB/space

> See patch 0003.
> 

+	 * because they correspond to flattneed UNION ALL subqueries.  Especially,

s/flattneed/flatten

> See patch 0004.
> 

+	 * no need to make any new entries, because anything that'd need those

Use "would" explicit

+ * this case, since it needn't be scanned.

, since it doesn't need to be scanned

> See patch 0005.
>
> See patch 0006.
>

I'll run some tests using a hash partitioned setup.

Best regards,
  Jesper




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-03-05 00:50  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Jesper Pedersen <jesper.pedersen@redhat.com>
  0 siblings, 2 replies; 265+ messages in thread

From: Amit Langote @ 2019-03-05 00:50 UTC (permalink / raw)
  To: jesper.pedersen@redhat.com; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; 'Amit Langote' <amitlangote09@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Justin Pryzby <pryzby@telsasoft.com>; pgsql-hackers

Hi Jesper,

Thanks for the review.  I've made all of the changes per your comments in
my local repository.  I'll post the updated patches after diagnosing what
I'm suspecting a memory over-allocation bug in one of the patches.  If you
configure build with --enable-cassert, you'll see that throughput
decreases as number of partitions run into many thousands, but it doesn't
when asserts are turned off.

On 2019/03/05 1:20, Jesper Pedersen wrote:
> I'll run some tests using a hash partitioned setup.

Thanks.

Regards,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* RE: speeding up planning with partitions
@ 2019-03-05 06:03  Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 1 reply; 265+ messages in thread

From: Imai, Yoshikazu @ 2019-03-05 06:03 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; jesper.pedersen@redhat.com <jesper.pedersen@redhat.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Justin Pryzby <pryzby@telsasoft.com>; pgsql-hackers

Amit-san,

On Tue, Mar 5, 2019 at 0:51 AM, Amit Langote wrote:
> Hi Jesper,
> 
> Thanks for the review.  I've made all of the changes per your comments
> in my local repository.  I'll post the updated patches after diagnosing
> what I'm suspecting a memory over-allocation bug in one of the patches.
> If you configure build with --enable-cassert, you'll see that throughput
> decreases as number of partitions run into many thousands, but it doesn't
> when asserts are turned off.
> 
> On 2019/03/05 1:20, Jesper Pedersen wrote:
> > I'll run some tests using a hash partitioned setup.
> 
> Thanks.

I've also done code review of 0001 and 0002 patch so far.

[0001]
1. Do we need to open/close a relation in add_appendrel_other_rels()? 

+	if (rel->part_scheme)
 	{
-		ListCell   *l;
    ...
-		Assert(cnt_parts == nparts);
+		rel->part_rels = (RelOptInfo **)
+				palloc0(sizeof(RelOptInfo *) * rel->nparts);
+		relation = table_open(rte->relid, NoLock);
 	}

+	if (relation)
+		table_close(relation, NoLock);


2. We can sophisticate the usage of Assert in add_appendrel_other_rels().

+	if (rel->part_scheme)
 	{
    ...
+		rel->part_rels = (RelOptInfo **)
+				palloc0(sizeof(RelOptInfo *) * rel->nparts);
+		relation = table_open(rte->relid, NoLock);
 	}
  ...
+	i  = 0;
+	foreach(l, root->append_rel_list)
+	{
    ...
+		if (rel->part_scheme != NULL)
+		{
+			Assert(rel->nparts > 0 && i < rel->nparts);
+			rel->part_rels[i] = childrel;
+		}
+
+		i++;

as below;

+	if (rel->part_scheme)
 	{
    ...
    Assert(rel->nparts > 0);
+		rel->part_rels = (RelOptInfo **)
+				palloc0(sizeof(RelOptInfo *) * rel->nparts);
+		relation = table_open(rte->relid, NoLock);
 	}
  ...
+	i  = 0;
+	foreach(l, root->append_rel_list)
+	{
    ...
+		if (rel->part_scheme != NULL)
+		{
+			Assert(i < rel->nparts);
+			rel->part_rels[i] = childrel;
+		}
+
+		i++;


[0002]
3. If using variable like is_source_inh_expansion, the code would be easy to read. (I might mistakenly understand root->simple_rel_array_size > 0 means source inheritance expansion though.)

In expand_inherited_rtentry() and expand_partitioned_rtentry():

+		 * Expand planner arrays for adding the child relations.  Can't do
+		 * this if we're not being called from query_planner.
+		 */
+		if (root->simple_rel_array_size > 0)
+		{
+			/* Needed when creating child RelOptInfos. */
+			rel = find_base_rel(root, rti);
+			expand_planner_arrays(root, list_length(inhOIDs));
+		}

+			/* Create the otherrel RelOptInfo too. */
+			if (rel)
+				(void) build_simple_rel(root, childRTindex, rel);

would be:

+		 * Expand planner arrays for adding the child relations.  Can't do
+		 * this if we're not being called from query_planner.
+		 */
+		if (is_source_inh_expansion)
+		{
+			/* Needed when creating child RelOptInfos. */
+			rel = find_base_rel(root, rti);
+			expand_planner_arrays(root, list_length(inhOIDs));
+		}

+			/* Create the otherrel RelOptInfo too. */
+			if (is_source_inh_expansion)
+				(void) build_simple_rel(root, childRTindex, rel);


4. I didn't see much carefully, but should the introduction of contains_inherit_children be in 0003 patch...?


I'll continue to do code review of rest patches.


--
Yoshikazu Imai 



^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-03-05 07:16  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
  0 siblings, 0 replies; 265+ messages in thread

From: Amit Langote @ 2019-03-05 07:16 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; jesper.pedersen@redhat.com <jesper.pedersen@redhat.com>; +Cc: 'Amit Langote' <amitlangote09@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Justin Pryzby <pryzby@telsasoft.com>; pgsql-hackers

Imai-san,

Thanks for checking.

On 2019/03/05 15:03, Imai, Yoshikazu wrote:
> I've also done code review of 0001 and 0002 patch so far.
> 
> [0001]
> 1. Do we need to open/close a relation in add_appendrel_other_rels()? 
> 
> +	if (rel->part_scheme)
>  	{
> -		ListCell   *l;
>     ...
> -		Assert(cnt_parts == nparts);
> +		rel->part_rels = (RelOptInfo **)
> +				palloc0(sizeof(RelOptInfo *) * rel->nparts);
> +		relation = table_open(rte->relid, NoLock);
>  	}
> 
> +	if (relation)
> +		table_close(relation, NoLock);

Ah, it should be moved to another patch.  Actually, to patch 0003, which
makes it necessary to inspect the PartitionDesc.

> 2. We can sophisticate the usage of Assert in add_appendrel_other_rels().
> 
> +	if (rel->part_scheme)
>  	{
>     ...
> +		rel->part_rels = (RelOptInfo **)
> +				palloc0(sizeof(RelOptInfo *) * rel->nparts);
> +		relation = table_open(rte->relid, NoLock);
>  	}
>   ...
> +	i  = 0;
> +	foreach(l, root->append_rel_list)
> +	{
>     ...
> +		if (rel->part_scheme != NULL)
> +		{
> +			Assert(rel->nparts > 0 && i < rel->nparts);
> +			rel->part_rels[i] = childrel;
> +		}
> +
> +		i++;
> 
> as below;
> 
> +	if (rel->part_scheme)
>  	{
>     ...
>     Assert(rel->nparts > 0);
> +		rel->part_rels = (RelOptInfo **)
> +				palloc0(sizeof(RelOptInfo *) * rel->nparts);
> +		relation = table_open(rte->relid, NoLock);
>  	}
>   ...
> +	i  = 0;
> +	foreach(l, root->append_rel_list)
> +	{
>     ...
> +		if (rel->part_scheme != NULL)
> +		{
> +			Assert(i < rel->nparts);
> +			rel->part_rels[i] = childrel;
> +		}
> +
> +		i++;

You're right.  That's what makes sense in this context.

> [0002]
> 3. If using variable like is_source_inh_expansion, the code would be easy to read. (I might mistakenly understand root->simple_rel_array_size > 0 means source inheritance expansion though.)

root->simple_rel_array_size > 0  *does* mean source inheritance expansion,
so you're not mistaken.  As you can see, expand_inherited_rtentry is
called by inheritance_planner() to expand target inheritance and by
add_appendrel_other_rels() to expand source inheritance.  Since the latter
is called by query_planner, simple_rel_array would have been initialized
and hence root->simple_rel_array_size > 0 is true.

Maybe it'd be a good idea to introduce is_source_inh_expansion variable
for clarity as you suggest and set it to (root->simple_rel_array_size > 0).

> 4. I didn't see much carefully, but should the introduction of contains_inherit_children be in 0003 patch...?

You're right.

Thanks again for the comments.  I've made changes to my local repository.

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-03-05 10:24  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 3 replies; 265+ messages in thread

From: Amit Langote @ 2019-03-05 10:24 UTC (permalink / raw)
  To: jesper.pedersen@redhat.com; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; 'Amit Langote' <amitlangote09@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Justin Pryzby <pryzby@telsasoft.com>; pgsql-hackers

On 2019/03/05 9:50, Amit Langote wrote:
> I'll post the updated patches after diagnosing what
> I'm suspecting a memory over-allocation bug in one of the patches.  If you
> configure build with --enable-cassert, you'll see that throughput
> decreases as number of partitions run into many thousands, but it doesn't
> when asserts are turned off.

Attached an updated version.  This incorporates fixes for both Jesper's
and Imai-san's review.  I haven't been able to pin down the bug (or
whatever) that makes throughput go down as the partition count increases,
when tested with a --enable-cassert build.

Thanks,
Amit

Attachments: download all (6 files, .tar.gz)

v27-0001-Build-other-rels-of-appendrel-baserels-in-a-sepa.patch (text/plain, 11.7K) download From 1e793976d1affae8aa6fa1c835752bad530132b6 Mon Sep 17 00:00:00 2001 From: Amit <amitlangote09@gmail.com> Date: Sat, 2 Mar 2019 14:13:13 +0900 Subject: [PATCH v27 1/6] Build "other rels" of appendrel baserels in a separate step Currently they're built in a stanza in build_simple_rel() which builds the child rels in the same invocation of build_simple_rel that's used to build the parent relation. Since query hasn't been processed to distribute restriction clauses to individual baserels at this point, we cannot perform partition pruning before adding the children. Newly added add_other_rels_to_query() runs *after* query_planner has distributed restriction clauses to base relations. This will allow us to use the clauses applied a given partitioned baserel to perform partition pruning, and add other rels for only the unpruned partitions. Later patches will do that. --- src/backend/optimizer/path/allpaths.c | 2 +- src/backend/optimizer/plan/initsplan.c | 60 ++++++++++++++++++-- src/backend/optimizer/plan/planmain.c | 15 +++-- src/backend/optimizer/util/relnode.c | 101 +++++++++++++++++++++------------ src/include/optimizer/pathnode.h | 2 + src/include/optimizer/planmain.h | 1 + 6 files changed, 135 insertions(+), 46 deletions(-) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 0debac75c6..8d8a8f17d5 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -1028,7 +1028,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, /* * The child rel's RelOptInfo was already created during - * add_base_rels_to_query. + * add_other_rels_to_query. */ childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 2afc3f1dfe..077d3203ba 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -20,6 +20,7 @@ #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" +#include "optimizer/inherit.h" #include "optimizer/joininfo.h" #include "optimizer/optimizer.h" #include "optimizer/pathnode.h" @@ -30,6 +31,7 @@ #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" #include "parser/analyze.h" +#include "parser/parsetree.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" @@ -97,10 +99,11 @@ static void check_hashjoinable(RestrictInfo *restrictinfo); * jtnode. Internally, the function recurses through the jointree. * * At the end of this process, there should be one baserel RelOptInfo for - * every non-join RTE that is used in the query. Therefore, this routine - * is the only place that should call build_simple_rel with reloptkind - * RELOPT_BASEREL. (Note: build_simple_rel recurses internally to build - * "other rel" RelOptInfos for the members of any appendrels we find here.) + * every non-join RTE that is specified in the query. Therefore, this + * routine is the only place that should call build_simple_rel with + * reloptkind RELOPT_BASEREL. (Note: "other rel" RelOptInfos for the + * members of any appendrels we find here are built later when query_planner + * calls add_other_rels_to_query().) */ void add_base_rels_to_query(PlannerInfo *root, Node *jtnode) @@ -133,6 +136,55 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode) (int) nodeTag(jtnode)); } +/* + * add_other_rels_to_query + * + * Scan the query's jointree and for each base rels that is an appendrel, + * create otherrel RelOptInfos of its children + * + * At the end of this process, there should be RelOptInfos for all relations + * that will be scanned by the query. + */ +void +add_other_rels_to_query(PlannerInfo *root, Node *jtnode) +{ + if (jtnode == NULL) + return; + if (IsA(jtnode, RangeTblRef)) + { + int varno = ((RangeTblRef *) jtnode)->rtindex; + RangeTblEntry *rte = rt_fetch(varno, root->parse->rtable); + + /* + * Only the parent subquery of a flattened UNION ALL and an inherited + * table can have children. + */ + if (rte->rtekind != RTE_SUBQUERY && rte->rtekind != RTE_RELATION) + return; + + if (rte->inh) + (void) add_appendrel_other_rels(root, rte, varno); + } + else if (IsA(jtnode, FromExpr)) + { + FromExpr *f = (FromExpr *) jtnode; + ListCell *l; + + foreach(l, f->fromlist) + add_other_rels_to_query(root, lfirst(l)); + } + else if (IsA(jtnode, JoinExpr)) + { + JoinExpr *j = (JoinExpr *) jtnode; + + add_other_rels_to_query(root, j->larg); + add_other_rels_to_query(root, j->rarg); + } + else + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(jtnode)); +} + /***************************************************************************** * diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c index 3cedd01c98..03c81772a3 100644 --- a/src/backend/optimizer/plan/planmain.c +++ b/src/backend/optimizer/plan/planmain.c @@ -159,10 +159,8 @@ query_planner(PlannerInfo *root, List *tlist, setup_append_rel_array(root); /* - * Construct RelOptInfo nodes for all base relations in query, and - * indirectly for all appendrel member relations ("other rels"). This - * will give us a RelOptInfo for every "simple" (non-join) rel involved in - * the query. + * Construct RelOptInfo nodes for all base relations directly mentioned + * in query, but not any appendrel member relations ("other rels") yet. * * Note: the reason we find the rels by searching the jointree and * appendrel list, rather than just scanning the rangetable, is that the @@ -204,6 +202,15 @@ query_planner(PlannerInfo *root, List *tlist, generate_base_implied_equalities(root); /* + * Now that we have restrict clauses figured out and assigned to proper + * base rels, we can proceed to add otherrels, that is, UNION ALL child + * tables, inheritance child tables. Having restrict clauses ready helps + * to exclude any children that wouldn't be necessary to scan, based on + * constraint exclusion and partition pruning. + */ + add_other_rels_to_query(root, (Node *) root->parse->jointree); + + /* * We have completed merging equivalence sets, so it's now possible to * generate pathkeys in canonical form; so compute query_pathkeys and * other pathkeys fields in PlannerInfo. diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 4130514952..a618950f88 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -16,19 +16,26 @@ #include <limits.h> +#include "access/table.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" +#include "optimizer/inherit.h" +#include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/placeholder.h" #include "optimizer/plancat.h" +#include "optimizer/planmain.h" #include "optimizer/prep.h" #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" #include "partitioning/partbounds.h" +#include "partitioning/partdesc.h" #include "utils/hsearch.h" +#include "utils/rel.h" typedef struct JoinHashEntry @@ -273,53 +280,73 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); + return rel; +} + +/* + * add_appendrel_other_rels + * This adds the "other rel" RelOptInfos a given "appendrel" baserel + * + * Parent relation in this case is the parent subquery in the flattened UNION + * ALL case or an inheritance parent table. + */ +void +add_appendrel_other_rels(PlannerInfo *root, RangeTblEntry *rte, Index rti) +{ + ListCell *l; + RelOptInfo *rel; + int i; + + rel = find_base_rel(root, rti); + /* - * If this rel is an appendrel parent, recurse to build "other rel" - * RelOptInfos for its children. They are "other rels" because they are - * not in the main join tree, but we will need RelOptInfos to plan access - * to them. + * For partitioned tables, we need to store the child RelOptInfos in the + * rel->part_rels array too. */ - if (rte->inh) + if (rel->part_scheme) + rel->part_rels = (RelOptInfo **) + palloc0(sizeof(RelOptInfo *) * rel->nparts); + + i = 0; + foreach(l, root->append_rel_list) { - ListCell *l; - int nparts = rel->nparts; - int cnt_parts = 0; + AppendRelInfo *appinfo = lfirst(l); + Index childRTindex = appinfo->child_relid; + RangeTblEntry *childrte; + RelOptInfo *childrel; - if (nparts > 0) - rel->part_rels = (RelOptInfo **) - palloc(sizeof(RelOptInfo *) * nparts); + if (appinfo->parent_relid != rti) + continue; - foreach(l, root->append_rel_list) + Assert(childRTindex < root->simple_rel_array_size); + childrte = root->simple_rte_array[childRTindex]; + Assert(childrte != NULL); + childrel = build_simple_rel(root, childRTindex, rel); + + /* + * For partitioned parents, we also need to add childrel to its + * part_rels array. The order in which child tables appear in + * append_rel_list is same as the order in which they appear in the + * parent's PartitionDesc, so assigning partitions like this works. + */ + if (rel->part_scheme != NULL) { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); - RelOptInfo *childrel; - - /* append_rel_list contains all append rels; ignore others */ - if (appinfo->parent_relid != relid) - continue; - - childrel = build_simple_rel(root, appinfo->child_relid, - rel); - - /* Nothing more to do for an unpartitioned table. */ - if (!rel->part_scheme) - continue; - - /* - * The order of partition OIDs in append_rel_list is the same as - * the order in the PartitionDesc, so the order of part_rels will - * also match the PartitionDesc. See expand_partitioned_rtentry. - */ - Assert(cnt_parts < nparts); - rel->part_rels[cnt_parts] = childrel; - cnt_parts++; + Assert(rel->nparts > 0 && i < rel->nparts); + rel->part_rels[i] = childrel; } - /* We should have seen all the child partitions. */ - Assert(cnt_parts == nparts); + i++; + + /* Child may itself be an inherited relation. */ + if (childrte->inh) + add_appendrel_other_rels(root, childrte, childRTindex); } - return rel; + /* + * For a partitioned table with non-zero number of partitions, we must + * have assigned all elements of its part_rels array. + */ + Assert(rel->nparts == 0 || i == rel->nparts); } /* diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 574bb85b50..1a07963a7d 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -279,6 +279,8 @@ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); +extern void add_appendrel_other_rels(PlannerInfo *root, RangeTblEntry *rte, + Index rti); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); extern RelOptInfo *find_join_rel(PlannerInfo *root, Relids relids); extern RelOptInfo *build_join_rel(PlannerInfo *root, diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index 3bbdb5e2f7..035caac500 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -65,6 +65,7 @@ extern int from_collapse_limit; extern int join_collapse_limit; extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode); +extern void add_other_rels_to_query(PlannerInfo *root, Node *jtnode); extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist); extern void add_vars_to_targetlist(PlannerInfo *root, List *vars, Relids where_needed, bool create_new_ph); -- 2.11.0
v27-0002-Delay-adding-inheritance-child-tables-until-quer.patch (text/plain, 33.3K) download From 0b11ae47f741f2ea6219e5d4c4697d5583f56c97 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 4 Mar 2019 15:10:29 +0900 Subject: [PATCH v27 2/6] Delay adding inheritance child tables until query_planner Inheritance child tables are now added by add_other_rels_to_query, which is called by query_planner. This replaces expand_inherited_tables called by subquery_planner as the method of adding child tables. At the point when add_other_rels_to_query is called, restriction clauses have already been assigned to individual base relations, so it will now be possible to perform partition pruning so that we know exact child tables to add in that case. This commit however doesn't change when pruning is performed. Since we're now adding children (and so any PlanRowMarks for them) much later, preprocess_targetlist cannot conclude which junk columns to add for a given "parent" PlanRowMark, because it depends on the value of allMarkTypes field of PlanRowMarks. The correct value of it cannot be determined until after we've seen all the child tables that will be scanned, so we must delay adding junk columns based on "parent" PlanRowMarks too. inheritance_planner now adds the child target tables on its own, because subquery_planner doesn't. Also, since it calls query_planner once for every child relation, source inheritance child tables are added multiple times. This both slows down queries when there are inherited tables in UPDATE/DELETE's FROM clause (source inheritance) and changes the EXPLAIN output a bit because source inheritance child tables' aliases come out different for every targer child subplan. Another patch will fix both these issues. --- contrib/postgres_fdw/expected/postgres_fdw.out | 24 ++--- src/backend/optimizer/plan/planner.c | 57 +++++++--- src/backend/optimizer/prep/preptlist.c | 131 ++++++++++++++--------- src/backend/optimizer/util/inherit.c | 141 +++++++++++++++++-------- src/backend/optimizer/util/plancat.c | 3 +- src/backend/optimizer/util/relnode.c | 61 ++++++++++- src/include/optimizer/inherit.h | 4 +- src/include/optimizer/pathnode.h | 1 + src/include/optimizer/prep.h | 2 + src/test/regress/expected/partition_prune.out | 12 +-- src/test/regress/expected/rowsecurity.out | 16 +-- 11 files changed, 314 insertions(+), 138 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 42108bd3d4..4d31cfed5d 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -7128,15 +7128,15 @@ select * from bar where f1 in (select f1 from foo) for update; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7166,15 +7166,15 @@ select * from bar where f1 in (select f1 from foo) for share; Output: bar2.f1, bar2.f2, bar2.ctid, bar2.*, bar2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 (23 rows) @@ -7203,15 +7203,15 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); -> Seq Scan on public.bar Output: bar.f1, bar.f2, bar.ctid -> Hash - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> HashAggregate - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid Group Key: foo.f1 -> Append -> Seq Scan on public.foo - Output: foo.ctid, foo.*, foo.tableoid, foo.f1 + Output: foo.f1, foo.ctid, foo.*, foo.tableoid -> Foreign Scan on public.foo2 - Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1 + Output: foo2.f1, foo2.ctid, foo2.*, foo2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index bc81535905..261c4e5661 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -25,6 +25,7 @@ #include "access/table.h" #include "access/xact.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" @@ -714,27 +715,24 @@ subquery_planner(PlannerGlobal *glob, Query *parse, } if (rte->lateral) root->hasLateralRTEs = true; + + /* + * While at it, also update the inh status. If the relation doesn't + * or can't have any children, there is no point in letting inh be set + * to true. Note that we do this before processing rowmarks, so that + * the correct information for setting isParent field of PlanRowMarks. + */ + if (rte->rtekind == RTE_RELATION) + rte->inh = rte->inh && has_subclass(rte->relid); } /* * Preprocess RowMark information. We need to do this after subquery - * pullup (so that all non-inherited RTEs are present) and before - * inheritance expansion (so that the info is available for - * expand_inherited_tables to examine and modify). + * pullup (so that all non-inherited RTEs are present). */ preprocess_rowmarks(root); /* - * Expand any rangetable entries that are inheritance sets into "append - * relations". This can add entries to the rangetable, but they must be - * plain RTE_RELATION entries, so it's OK (and marginally more efficient) - * to do it after checking for joins and other special RTEs. We must do - * this after pulling up subqueries, else we'd fail to handle inherited - * tables in subqueries. - */ - expand_inherited_tables(root); - - /* * Set hasHavingQual to remember if HAVING clause is present. Needed * because preprocess_expression will reduce a constant-true condition to * an empty qual list ... but "HAVING TRUE" is not a semantic no-op. @@ -1210,10 +1208,35 @@ inheritance_planner(PlannerInfo *root) Query *parent_parse; Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); PlannerInfo **parent_roots = NULL; + List *orig_append_rel_list = list_copy(root->append_rel_list); Assert(parse->commandType != CMD_INSERT); /* + * Add child target relations. Note that this only adds the children of + * the query's target relation and no other. Especially, children of any + * source relations are added when the loop below calls grouping_planner + * on the *1st* child target relation. + */ + parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); + + expand_inherited_rtentry(root, parent_rte, top_parentRTindex); + + /* + * If parent no longer has any children, then treat this as an update of + * a single table. + */ + if (!parent_rte->inh) + { + /* + * We'll be retrieving all tuples to modify, so passing 0.0 for + * tuple_fraction. + */ + grouping_planner(root, false, 0.0); + return; + } + + /* * We generate a modified instance of the original Query for each target * relation, plan that, and put all the plans into a list that will be * controlled by a single ModifyTable node. All the instances share the @@ -1274,7 +1297,6 @@ inheritance_planner(PlannerInfo *root) * not appear anywhere else in the plan, so the confusion explained below * for non-partitioning inheritance cases is not possible. */ - parent_rte = rt_fetch(top_parentRTindex, root->parse->rtable); if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE) { nominalRelation = top_parentRTindex; @@ -1325,6 +1347,9 @@ inheritance_planner(PlannerInfo *root) subroot = makeNode(PlannerInfo); memcpy(subroot, parent_root, sizeof(PlannerInfo)); + /* grouping_planner doesn't need to see the target children. */ + subroot->append_rel_list = list_copy(orig_append_rel_list); + /* * Generate modified query with this rel as target. We first apply * adjust_appendrel_attrs, which copies the Query and changes @@ -2618,7 +2643,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = rc->strength; newrc->waitPolicy = rc->waitPolicy; - newrc->isParent = false; + newrc->isParent = rte->inh; prowmarks = lappend(prowmarks, newrc); } @@ -2643,7 +2668,7 @@ preprocess_rowmarks(PlannerInfo *root) newrc->allMarkTypes = (1 << newrc->markType); newrc->strength = LCS_NONE; newrc->waitPolicy = LockWaitBlock; /* doesn't matter */ - newrc->isParent = false; + newrc->isParent = rte->rtekind == RTE_RELATION ? rte->inh : false; prowmarks = lappend(prowmarks, newrc); } diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 5392d1a561..41578f2653 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -126,61 +126,23 @@ preprocess_targetlist(PlannerInfo *root) foreach(lc, root->rowMarks) { PlanRowMark *rc = (PlanRowMark *) lfirst(lc); - Var *var; - char resname[32]; - TargetEntry *tle; + List *junk_tles; /* child rels use the same junk attrs as their parents */ if (rc->rti != rc->prti) continue; - if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) - { - /* Need to fetch TID */ - var = makeVar(rc->rti, - SelfItemPointerAttributeNumber, - TIDOID, - -1, - InvalidOid, - 0); - snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - } - if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) - { - /* Need the whole row as a junk var */ - var = makeWholeRowVar(rt_fetch(rc->rti, range_table), - rc->rti, - 0, - false); - snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - } - - /* If parent of inheritance tree, always fetch the tableoid too. */ + /* + * For inheritance parent row marks, we defer adding junk columns + * until we've added child row marks, because some children might + * require different row mark types which will change the parent row + * mark's allMarkTypes fields. + */ if (rc->isParent) - { - var = makeVar(rc->rti, - TableOidAttributeNumber, - OIDOID, - -1, - InvalidOid, - 0); - snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); - tle = makeTargetEntry((Expr *) var, - list_length(tlist) + 1, - pstrdup(resname), - true); - tlist = lappend(tlist, tle); - } + continue; + + junk_tles = get_rowmark_junk_tles(root, tlist, rc); + tlist = list_concat(tlist, junk_tles); } /* @@ -434,3 +396,74 @@ get_plan_rowmark(List *rowmarks, Index rtindex) } return NULL; } + +/* + * get_rowmark_junk_tles + * Returns TLEs for junk columns necessary for implementing given + * PlanRowMark + * + * These TLEs are needed for locking of rels selected FOR UPDATE/SHARE, and + * to do EvalPlanQual rechecking. See comments for PlanRowMark. + */ +List * +get_rowmark_junk_tles(PlannerInfo *root, List *tlist, PlanRowMark *rc) +{ + List *range_table = root->parse->rtable; + int tlist_len = list_length(tlist); + List *junk_tles = NIL; + Var *var; + char resname[32]; + TargetEntry *tle; + + if (rc->allMarkTypes & ~(1 << ROW_MARK_COPY)) + { + /* Need to fetch TID */ + var = makeVar(rc->rti, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "ctid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + tlist_len + 1, + pstrdup(resname), + true); + junk_tles = lappend(junk_tles, tle); + tlist_len++; + } + if (rc->allMarkTypes & (1 << ROW_MARK_COPY)) + { + /* Need the whole row as a junk var */ + var = makeWholeRowVar(rt_fetch(rc->rti, range_table), + rc->rti, + 0, + false); + snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + tlist_len + 1, + pstrdup(resname), + true); + junk_tles = lappend(junk_tles, tle); + tlist_len++; + } + + /* For inheritance cases, always fetch the tableoid too. */ + if (rc->isParent) + { + var = makeVar(rc->rti, + TableOidAttributeNumber, + OIDOID, + -1, + InvalidOid, + 0); + snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId); + tle = makeTargetEntry((Expr *) var, + tlist_len + 1, + pstrdup(resname), + true); + junk_tles = lappend(junk_tles, tle); + } + + return junk_tles; +} diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index a014a12060..697de74505 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -21,14 +21,14 @@ #include "miscadmin.h" #include "optimizer/appendinfo.h" #include "optimizer/inherit.h" +#include "optimizer/pathnode.h" +#include "optimizer/planmain.h" #include "optimizer/planner.h" #include "optimizer/prep.h" #include "partitioning/partdesc.h" #include "utils/rel.h" -static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, - Index rti); static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, Index parentRTindex, Relation parentrel, @@ -45,36 +45,6 @@ static Bitmapset *translate_col_privs(const Bitmapset *parent_privs, /* - * expand_inherited_tables - * Expand each rangetable entry that represents an inheritance set - * into an "append relation". At the conclusion of this process, - * the "inh" flag is set in all and only those RTEs that are append - * relation parents. - */ -void -expand_inherited_tables(PlannerInfo *root) -{ - Index nrtes; - Index rti; - ListCell *rl; - - /* - * expand_inherited_rtentry may add RTEs to parse->rtable. The function is - * expected to recursively handle any RTEs that it creates with inh=true. - * So just scan as far as the original end of the rtable list. - */ - nrtes = list_length(root->parse->rtable); - rl = list_head(root->parse->rtable); - for (rti = 1; rti <= nrtes; rti++) - { - RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl); - - expand_inherited_rtentry(root, rte, rti); - rl = lnext(rl); - } -} - -/* * expand_inherited_rtentry * Check whether a rangetable entry represents an inheritance set. * If so, add entries for all the child tables to the query's @@ -94,7 +64,7 @@ expand_inherited_tables(PlannerInfo *root) * Since a partitioned table is not scanned, it might have only one associated * AppendRelInfo. */ -static void +void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) { Oid parentOID; @@ -160,6 +130,8 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) List *appinfos = NIL; RangeTblEntry *childrte; Index childRTindex; + bool is_source_inh_expansion; + RelOptInfo *rel = NULL; /* Scan for all members of inheritance set, acquire needed locks */ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL); @@ -178,6 +150,20 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } /* + * If parent is a source relation of the query, we'd need to + * add the child RelOptInfos as well in addition to RangeTblEntry's + * and AppendRelInfo's. We can tell it's source relation by noticing + * that simple_rel_array has been set up by query_planner. + */ + is_source_inh_expansion = (root->simple_rel_array_size > 0); + if (is_source_inh_expansion) + { + rel = find_base_rel(root, rti); + /* Expand various arrays in PlannerInfo to hold child object. */ + expand_planner_arrays(root, list_length(inhOIDs)); + } + + /* * This table has no partitions. Expand any plain inheritance * children in the order the OIDs were returned by * find_all_inheritors. @@ -210,6 +196,10 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) &appinfos, &childrte, &childRTindex); + /* Create the otherrel RelOptInfo too. */ + if (is_source_inh_expansion) + (void) build_simple_rel(root, childRTindex, rel); + /* Close child relations, but keep locks */ if (childOID != parentOID) table_close(newrelation, NoLock); @@ -230,6 +220,19 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti) } + /* + * Add junk columns needed by the row mark if any and also add the + * relevant expressions to the root parent's reltarget. + */ + if (oldrc) + { + List *tlist = root->processed_tlist; + List *junk_tles = get_rowmark_junk_tles(root, tlist, oldrc); + + build_base_rel_tlists(root, junk_tles); + root->processed_tlist = list_concat(root->processed_tlist, junk_tles); + } + table_close(oldrelation, NoLock); } @@ -247,6 +250,8 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, RangeTblEntry *childrte; Index childRTindex; PartitionDesc partdesc = RelationGetPartitionDesc(parentrel); + RelOptInfo *rel = NULL; + bool is_source_inh_expansion; check_stack_depth(); @@ -266,6 +271,35 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, root->partColsUpdated = has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + /* + * If parent is a source relation of the query, we'd need to + * add the child RelOptInfos as well in addition to RangeTblEntry's + * and AppendRelInfo's. We can tell it's source relation by noticing + * that simple_rel_array has been set up by query_planner. + */ + is_source_inh_expansion = (root->simple_rel_array_size > 0); + if (is_source_inh_expansion) + { + rel = find_base_rel(root, parentRTindex); + + /* + * Expand simple_rel_array and friends to hold child objects. + * + * We'll need nparts + 1 new slots, because we also consider parent + * as a child relation. FIXME: no longer add the parent as child + */ + expand_planner_arrays(root, partdesc->nparts + 1); + + /* + * We also store partition RelOptInfo pointers in the parent + * relation. + */ + Assert(rel->part_rels == NULL); + if (partdesc->nparts > 0) + rel->part_rels = (RelOptInfo **) palloc0(rel->nparts * + sizeof(RelOptInfo *)); + } + /* First expand the partitioned table itself. */ expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, top_parentrc, parentrel, @@ -301,8 +335,12 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, parentrel, top_parentrc, childrel, appinfos, &childrte, &childRTindex); + /* Create the otherrel RelOptInfo too. */ + if (is_source_inh_expansion) + rel->part_rels[i] = build_simple_rel(root, childRTindex, rel); + /* If this child is itself partitioned, recurse */ - if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + if (childrte->inh) expand_partitioned_rtentry(root, childrte, childRTindex, childrel, top_parentrc, lockmode, appinfos); @@ -345,7 +383,8 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, Oid childOID = RelationGetRelid(childrel); RangeTblEntry *childrte; Index childRTindex; - AppendRelInfo *appinfo; + AppendRelInfo *appinfo = NULL; + PartitionDesc childrel_partdesc = RelationGetPartitionDesc(childrel); /* * Build an RTE for the child, and attach to query's rangetable list. We @@ -363,9 +402,14 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childrte_p = childrte; childrte->relid = childOID; childrte->relkind = childrel->rd_rel->relkind; - /* A partitioned child will need to be expanded further. */ + /* + * A partitioned child will need to be expanded further, but only if it + * actually has partitions. A partitioned table with zero children is + * specially handled in set_rel_size(). + */ if (childOID != parentOID && - childrte->relkind == RELKIND_PARTITIONED_TABLE) + childrte->relkind == RELKIND_PARTITIONED_TABLE && + childrel_partdesc->nparts > 0) childrte->inh = true; else childrte->inh = false; @@ -376,13 +420,11 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, *childRTindex_p = childRTindex; /* - * We need an AppendRelInfo if paths will be built for the child RTE. If - * childrte->inh is true, then we'll always need to generate append paths - * for it. If childrte->inh is false, we must scan it if it's not a - * partitioned table; but if it is a partitioned table, then it never has - * any data of its own and need not be scanned. + * Don't need an AppendRelInfo for duplicate RTEs we create for + * partitioned table parent rels. */ - if (childrte->relkind != RELKIND_PARTITIONED_TABLE || childrte->inh) + if (childrte->relid != parentOID || + childrte->relkind != RELKIND_PARTITIONED_TABLE) { appinfo = make_append_rel_info(parentrel, childrel, parentRTindex, childRTindex); @@ -409,6 +451,19 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, } /* + * Store the RTE and appinfo in the PlannerInfo, which the caller must + * already have allocated space for. + */ + if (root->simple_rel_array_size > 0) + { + Assert(childRTindex < root->simple_rel_array_size); + Assert(root->simple_rte_array[childRTindex] == NULL); + root->simple_rte_array[childRTindex] = childrte; + Assert(root->append_rel_array[childRTindex] == NULL); + root->append_rel_array[childRTindex] = appinfo; + } + + /* * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE. */ if (top_parentrc) diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 78a96b4ee2..549af9dc6e 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -2090,7 +2090,8 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); + if (partdesc->nparts > 0) + rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); rel->nparts = partdesc->nparts; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index a618950f88..58b19307af 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -139,6 +139,50 @@ setup_append_rel_array(PlannerInfo *root) } /* + * expand_planner_arrays + * Expand the PlannerInfo arrays by add_size members and initialize the + * the newly added bytes with zero + */ +void +expand_planner_arrays(PlannerInfo *root, int add_size) +{ + int new_size; + + Assert(add_size > 0); + + new_size = root->simple_rel_array_size + add_size; + + /* Expand various arrays and 0-initialize added bytes. */ + root->simple_rte_array = (RangeTblEntry **) + repalloc(root->simple_rte_array, + sizeof(RangeTblEntry *) * new_size); + MemSet(root->simple_rte_array + root->simple_rel_array_size, + 0, sizeof(RangeTblEntry *) * add_size); + root->simple_rel_array = (RelOptInfo **) + repalloc(root->simple_rel_array, + sizeof(RelOptInfo *) * new_size); + MemSet(root->simple_rel_array + root->simple_rel_array_size, + 0, sizeof(RelOptInfo *) * add_size); + + if (root->append_rel_array) + { + root->append_rel_array = (AppendRelInfo **) + repalloc(root->append_rel_array, + sizeof(AppendRelInfo *) * new_size); + MemSet(root->append_rel_array + root->simple_rel_array_size, + 0, sizeof(AppendRelInfo *) * add_size); + } + else + { + root->append_rel_array = (AppendRelInfo **) + palloc0(sizeof(AppendRelInfo *) * + new_size); + } + + root->simple_rel_array_size = new_size; +} + +/* * build_simple_rel * Construct a new RelOptInfo for a base relation or 'other' relation. */ @@ -297,6 +341,18 @@ add_appendrel_other_rels(PlannerInfo *root, RangeTblEntry *rte, Index rti) RelOptInfo *rel; int i; + /* + * Add inheritance children to the query. For child tables that are + * themselves partitioned, their children will be added recursively. + */ + if (rte->rtekind == RTE_RELATION) + { + expand_inherited_rtentry(root, rte, rti); + return; + } + + /* Add other rels of flattened UNION ALL children. */ + Assert(rte->rtekind == RTE_SUBQUERY); rel = find_base_rel(root, rti); /* @@ -304,8 +360,11 @@ add_appendrel_other_rels(PlannerInfo *root, RangeTblEntry *rte, Index rti) * rel->part_rels array too. */ if (rel->part_scheme) + { + Assert(rel->nparts > 0); rel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * rel->nparts); + } i = 0; foreach(l, root->append_rel_list) @@ -331,7 +390,7 @@ add_appendrel_other_rels(PlannerInfo *root, RangeTblEntry *rte, Index rti) */ if (rel->part_scheme != NULL) { - Assert(rel->nparts > 0 && i < rel->nparts); + Assert(i < rel->nparts); rel->part_rels[i] = childrel; } diff --git a/src/include/optimizer/inherit.h b/src/include/optimizer/inherit.h index d2418f15cf..427c1d0abe 100644 --- a/src/include/optimizer/inherit.h +++ b/src/include/optimizer/inherit.h @@ -16,7 +16,7 @@ #include "nodes/pathnodes.h" - -extern void expand_inherited_tables(PlannerInfo *root); +extern void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, + Index rti); #endif /* INHERIT_H */ diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 1a07963a7d..60361507d2 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -277,6 +277,7 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path, */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern void setup_append_rel_array(PlannerInfo *root); +extern void expand_planner_arrays(PlannerInfo *root, int add_size); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent); extern void add_appendrel_other_rels(PlannerInfo *root, RangeTblEntry *rte, diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index a9b2c9026c..5979c9885e 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -37,6 +37,8 @@ extern Relids get_relids_for_join(Query *query, int joinrelid); extern List *preprocess_targetlist(PlannerInfo *root); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); +extern List *get_rowmark_junk_tles(PlannerInfo *root, List *tlist, + PlanRowMark *rc); /* * prototypes for prepunion.c diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index 30946f77b6..71942394ba 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -2559,16 +2559,16 @@ update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a; Index Cond: (a = 1) -> Nested Loop (actual rows=1 loops=1) -> Append (actual rows=1 loops=1) - -> Bitmap Heap Scan on ab_a1_b1 ab_a1_b1_1 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b1 ab_a1_b1_2 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b2 ab_a1_b2_1 (actual rows=1 loops=1) + -> Bitmap Heap Scan on ab_a1_b2 ab_a1_b2_2 (actual rows=1 loops=1) Recheck Cond: (a = 1) Heap Blocks: exact=1 -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_1 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_2 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) @@ -2580,16 +2580,16 @@ update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a; Index Cond: (a = 1) -> Nested Loop (actual rows=0 loops=1) -> Append (actual rows=1 loops=1) - -> Bitmap Heap Scan on ab_a1_b1 ab_a1_b1_1 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b1 ab_a1_b1_3 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b2 ab_a1_b2_1 (actual rows=1 loops=1) + -> Bitmap Heap Scan on ab_a1_b2 ab_a1_b2_3 (actual rows=1 loops=1) Recheck Cond: (a = 1) Heap Blocks: exact=1 -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_1 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_3 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out index 2e170497c9..a6a499ed4a 100644 --- a/src/test/regress/expected/rowsecurity.out +++ b/src/test/regress/expected/rowsecurity.out @@ -1815,26 +1815,26 @@ AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2; -> Seq Scan on t3 t1_2_2 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Nested Loop - Join Filter: (t1_1_1.b = t1_2.b) + Join Filter: (t1_1_1.b = t1_2_3.b) -> Seq Scan on t2 t1_1_1 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Append - -> Seq Scan on t1 t1_2 + -> Seq Scan on t1 t1_2_3 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t2 t1_2_1 + -> Seq Scan on t2 t1_2_4 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t3 t1_2_2 + -> Seq Scan on t3 t1_2_5 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Nested Loop - Join Filter: (t1_1_2.b = t1_2.b) + Join Filter: (t1_1_2.b = t1_2_6.b) -> Seq Scan on t3 t1_1_2 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Append - -> Seq Scan on t1 t1_2 + -> Seq Scan on t1 t1_2_6 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t2 t1_2_1 + -> Seq Scan on t2 t1_2_7 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t3 t1_2_2 + -> Seq Scan on t3 t1_2_8 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) (37 rows) -- 2.11.0
v27-0003-Adjust-inheritance_planner-to-reuse-source-child.patch (text/plain, 20.7K) download From add94833a1d34716b3b39b0f403aa37b4950d78c Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 4 Mar 2019 15:52:11 +0900 Subject: [PATCH v27 3/6] Adjust inheritance_planner to reuse source child tables Set of source inheritance child tables won't change across repeated planning of the query for different target child relations. So, note down the RangeTblEntrys, AppendRelInfos, and PlanRowMarks of the source inheritance child tables after the planning for the first child table is finished. When planning for the subsequent child target tables, put the saved objects of source inheritance child tables into the child PlannerInfos and set contains_inherit_children to true, so that query_planner doesn't add them again. This restores the regression test outputs to their original state, because like before, source inheritance child tables are not added multiple times. --- src/backend/nodes/outfuncs.c | 1 + src/backend/optimizer/plan/planner.c | 217 +++++++++++++++++--------- src/backend/optimizer/prep/preptlist.c | 6 +- src/backend/optimizer/util/relnode.c | 15 +- src/include/nodes/pathnodes.h | 7 + src/test/regress/expected/partition_prune.out | 12 +- src/test/regress/expected/rowsecurity.out | 16 +- 7 files changed, 180 insertions(+), 94 deletions(-) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 65302fe65b..3e3c283340 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2218,6 +2218,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_BITMAPSET_FIELD(curOuterRels); WRITE_NODE_FIELD(curOuterParams); WRITE_BOOL_FIELD(partColsUpdated); + WRITE_BOOL_FIELD(contains_inherit_children); } static void diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 261c4e5661..e3016d6a42 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -642,6 +642,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->wt_param_id = -1; root->non_recursive_path = NULL; root->partColsUpdated = false; + root->contains_inherit_children = false; /* * If there is a WITH list, process each WITH query and either convert it @@ -1205,10 +1206,15 @@ inheritance_planner(PlannerInfo *root) Index rti; RangeTblEntry *parent_rte; PlannerInfo *parent_root; - Query *parent_parse; Bitmapset *parent_relids = bms_make_singleton(top_parentRTindex); PlannerInfo **parent_roots = NULL; List *orig_append_rel_list = list_copy(root->append_rel_list); + List *orig_row_marks = list_copy(root->rowMarks); + List *orig_rtable = list_copy(root->parse->rtable); + List *rtable_with_target; + List *source_appinfos = NIL; + List *source_child_rowmarks = NIL; + List *source_child_rtes = NIL; Assert(parse->commandType != CMD_INSERT); @@ -1236,6 +1242,13 @@ inheritance_planner(PlannerInfo *root) return; } + + /* + * This one also contains the child target relations, but no other + * child relations. + */ + rtable_with_target = list_copy(root->parse->rtable); + /* * We generate a modified instance of the original Query for each target * relation, plan that, and put all the plans into a list that will be @@ -1252,10 +1265,13 @@ inheritance_planner(PlannerInfo *root) * management of the rowMarks list. * * To begin with, generate a bitmapset of the relids of the subquery RTEs. + * We use orig_rtable, not rtable_with_target (parse->rtable), because we + * wouldn't need to consider any newly added RTEs as they all must be + * RTE_RELATION entries. */ subqueryRTindexes = NULL; rti = 1; - foreach(lc, parse->rtable) + foreach(lc, orig_rtable) { RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); @@ -1268,26 +1284,24 @@ inheritance_planner(PlannerInfo *root) * Next, we want to identify which AppendRelInfo items contain references * to any of the aforesaid subquery RTEs. These items will need to be * copied and modified to adjust their subquery references; whereas the - * other ones need not be touched. It's worth being tense over this - * because we can usually avoid processing most of the AppendRelInfo - * items, thereby saving O(N^2) space and time when the target is a large - * inheritance tree. We can identify AppendRelInfo items by their - * child_relid, since that should be unique within the list. + * other ones need not be touched. We can assume that all (if any) + * entries in orig_append_rel_list contain references to subquery RTEs, + * because they correspond to flattened UNION ALL subqueries. Especially, + * we don't need to bother with those added when adding the child target + * relations. We can identify AppendRelInfo items by their child_relid, + * since that should be unique within the list. */ modifiableARIindexes = NULL; - if (subqueryRTindexes != NULL) + foreach(lc, orig_append_rel_list) { - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); + AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc); - if (bms_is_member(appinfo->parent_relid, subqueryRTindexes) || - bms_is_member(appinfo->child_relid, subqueryRTindexes) || - bms_overlap(pull_varnos((Node *) appinfo->translated_vars), - subqueryRTindexes)) - modifiableARIindexes = bms_add_member(modifiableARIindexes, - appinfo->child_relid); - } + Assert(bms_is_member(appinfo->parent_relid, subqueryRTindexes) || + bms_is_member(appinfo->child_relid, subqueryRTindexes) || + bms_overlap(pull_varnos((Node *) appinfo->translated_vars), + subqueryRTindexes)); + modifiableARIindexes = bms_add_member(modifiableARIindexes, + appinfo->child_relid); } /* @@ -1338,7 +1352,6 @@ inheritance_planner(PlannerInfo *root) */ parent_root = parent_roots[appinfo->parent_relid]; Assert(parent_root != NULL); - parent_parse = parent_root->parse; /* * We need a working copy of the PlannerInfo so that we can control @@ -1347,8 +1360,51 @@ inheritance_planner(PlannerInfo *root) subroot = makeNode(PlannerInfo); memcpy(subroot, parent_root, sizeof(PlannerInfo)); - /* grouping_planner doesn't need to see the target children. */ - subroot->append_rel_list = list_copy(orig_append_rel_list); + /* + * Per the above comment, we'll be changing only the AppendRelInfos + * that are contained in orig_append_rel_list, so only copy those. + */ + subroot->append_rel_list = copyObject(orig_append_rel_list); + + /* + * Likewise, for PlanRowMarks. (Fortunately, the executor doesn't + * need to see the modified copies --- we can just pass it the + * original rowMarks list.) + */ + subroot->rowMarks = copyObject(orig_row_marks); + + /* + * No need to copy of the RTEs themselves, but do copy the List + * structure. + */ + subroot->parse->rtable = list_copy(rtable_with_target); + + /* + * If this isn't the first child query, then we can use the child + * objects for source child relations created during the planning of + * 1st child query. IOW, this planning run doesn't need to create the + * child objects again, indicated by setting contains_inherit_children + * sub-PlannerInfo. + */ + if (source_appinfos) + { + subroot->append_rel_list = list_concat(subroot->append_rel_list, + source_appinfos); + /* + * XXX Assert(source_child_rowmarks != NIL); + */ + subroot->rowMarks = list_concat(subroot->rowMarks, + source_child_rowmarks); + Assert(source_child_rtes != NIL); + subroot->parse->rtable = list_concat(subroot->parse->rtable, + source_child_rtes); + + /* + * We have the children, so no need to add them again during this + * planning cycle. + */ + subroot->contains_inherit_children = true; + } /* * Generate modified query with this rel as target. We first apply @@ -1358,7 +1414,7 @@ inheritance_planner(PlannerInfo *root) */ subroot->parse = (Query *) adjust_appendrel_attrs(parent_root, - (Node *) parent_parse, + (Node *) subroot->parse, 1, &appinfo); /* @@ -1424,41 +1480,6 @@ inheritance_planner(PlannerInfo *root) nominalRelation = appinfo->child_relid; /* - * The rowMarks list might contain references to subquery RTEs, so - * make a copy that we can apply ChangeVarNodes to. (Fortunately, the - * executor doesn't need to see the modified copies --- we can just - * pass it the original rowMarks list.) - */ - subroot->rowMarks = copyObject(parent_root->rowMarks); - - /* - * The append_rel_list likewise might contain references to subquery - * RTEs (if any subqueries were flattenable UNION ALLs). So prepare - * to apply ChangeVarNodes to that, too. As explained above, we only - * want to copy items that actually contain such references; the rest - * can just get linked into the subroot's append_rel_list. - * - * If we know there are no such references, we can just use the outer - * append_rel_list unmodified. - */ - if (modifiableARIindexes != NULL) - { - ListCell *lc2; - - subroot->append_rel_list = NIL; - foreach(lc2, parent_root->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, modifiableARIindexes)) - appinfo2 = copyObject(appinfo2); - - subroot->append_rel_list = lappend(subroot->append_rel_list, - appinfo2); - } - } - - /* * Add placeholders to the child Query's rangetable list to fill the * RT indexes already reserved for subqueries in previous children. * These won't be referenced, so there's no need to make them very @@ -1471,23 +1492,23 @@ inheritance_planner(PlannerInfo *root) /* * If this isn't the first child Query, generate duplicates of all * subquery RTEs, and adjust Var numbering to reference the - * duplicates. To simplify the loop logic, we scan the original rtable - * not the copy just made by adjust_appendrel_attrs; that should be OK - * since subquery RTEs couldn't contain any references to the target - * rel. + * duplicates. Note that we scan the original rtable before any + * child target relations were added, which is OK, because no other + * RTEs would contain references to subquery rels being modified. */ if (final_rtable != NIL && subqueryRTindexes != NULL) { ListCell *lr; rti = 1; - foreach(lr, parent_parse->rtable) + foreach(lr, orig_rtable) { RangeTblEntry *rte = lfirst_node(RangeTblEntry, lr); if (bms_is_member(rti, subqueryRTindexes)) { Index newrti; + ListCell *lc2; /* * The RTE can't contain any references to its own RT @@ -1498,19 +1519,21 @@ inheritance_planner(PlannerInfo *root) newrti = list_length(subroot->parse->rtable) + 1; ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); ChangeVarNodes((Node *) subroot->rowMarks, rti, newrti, 0); - /* Skip processing unchanging parts of append_rel_list */ - if (modifiableARIindexes != NULL) + + /* + * UNION ALL related appinfos are the beginning of the + * list, only change those. + */ + foreach(lc2, subroot->append_rel_list) { - ListCell *lc2; + AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, + lc2); - foreach(lc2, subroot->append_rel_list) - { - AppendRelInfo *appinfo2 = lfirst_node(AppendRelInfo, lc2); - - if (bms_is_member(appinfo2->child_relid, - modifiableARIindexes)) - ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); - } + if (bms_is_member(appinfo2->child_relid, + modifiableARIindexes)) + ChangeVarNodes((Node *) appinfo2, rti, newrti, 0); + else + break; } rte = copyObject(rte); ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0); @@ -1539,6 +1562,54 @@ inheritance_planner(PlannerInfo *root) subpath = sub_final_rel->cheapest_total_path; /* + * If we finished planning our first child query, copy the source + * child objects that were added during its planning. + */ + if (source_appinfos == NIL && subroot->append_rel_list) + { + int num_skip_appinfos = list_length(orig_append_rel_list); + int num_skip_rowmarks = list_length(orig_row_marks); + int num_skip_rtes = list_length(rtable_with_target); + ListCell *lc2; + + source_appinfos = list_copy_tail(subroot->append_rel_list, + num_skip_appinfos); + Assert(source_child_rowmarks == NIL); + source_child_rowmarks = list_copy_tail(subroot->rowMarks, + num_skip_rowmarks); + Assert(source_child_rtes == NIL); + source_child_rtes = list_copy_tail(subroot->parse->rtable, + num_skip_rtes); + + /* + * Original parent PlanRowMark is modified when adding the + * child PlanRowMarks. Copy those changes so that the planning + * of subsequent child queries works correctly. That is + * necessary, because we won't be adding child objects again, + * so there won't be an opportunity to modify the parent + * PlanRowMark as desired. + * + * All the original parent row marks are the beginning of + * subroot->rowMarks, skip the rest. + */ + foreach(lc2, orig_row_marks) + { + PlanRowMark *oldrc = lfirst_node(PlanRowMark, lc2); + ListCell *lc3; + + foreach(lc3, subroot->rowMarks) + { + PlanRowMark *newrc = lfirst_node(PlanRowMark, lc3); + + if (oldrc->rti == newrc->rti) + oldrc->allMarkTypes = newrc->allMarkTypes; + else + break; + } + } + } + + /* * If this child rel was excluded by constraint exclusion, exclude it * from the result plan. */ diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 41578f2653..2dbfecb01c 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -136,9 +136,11 @@ preprocess_targetlist(PlannerInfo *root) * For inheritance parent row marks, we defer adding junk columns * until we've added child row marks, because some children might * require different row mark types which will change the parent row - * mark's allMarkTypes fields. + * mark's allMarkTypes fields. If root already contains child row + * marks, we can assume parent row marks' allMarkTypes is already + * correct. */ - if (rc->isParent) + if (rc->isParent && !root->contains_inherit_children) continue; junk_tles = get_rowmark_junk_tles(root, tlist, rc); diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 58b19307af..6d960582f3 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -342,17 +342,22 @@ add_appendrel_other_rels(PlannerInfo *root, RangeTblEntry *rte, Index rti) int i; /* - * Add inheritance children to the query. For child tables that are - * themselves partitioned, their children will be added recursively. + * Add inheritance children to the query if not already done. For child + * tables that are themselves partitioned, their children will be added + * recursively. */ - if (rte->rtekind == RTE_RELATION) + if (rte->rtekind == RTE_RELATION && !root->contains_inherit_children) { expand_inherited_rtentry(root, rte, rti); return; } - /* Add other rels of flattened UNION ALL children. */ - Assert(rte->rtekind == RTE_SUBQUERY); + /* + * Add other rels. We get here for non-subquery RTEs only if root + * already contains inheritance childern but we still need to create + * RelOptInfos for them. + */ + Assert(rte->rtekind == RTE_SUBQUERY || rte->rtekind == RTE_RELATION); rel = find_base_rel(root, rti); /* diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index a008ae07da..c2e292e54c 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -348,6 +348,13 @@ struct PlannerInfo /* Does this query modify any partition key columns? */ bool partColsUpdated; + + /* + * Does this PlannerInfo and its Query object contain *all* inheritance + * children? If true, the RTEs, the AppendRelInfos, and the PlanRowMarks + * of all the children are assumed to be present. + */ + bool contains_inherit_children; }; diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index 71942394ba..30946f77b6 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -2559,16 +2559,16 @@ update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a; Index Cond: (a = 1) -> Nested Loop (actual rows=1 loops=1) -> Append (actual rows=1 loops=1) - -> Bitmap Heap Scan on ab_a1_b1 ab_a1_b1_2 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b1 ab_a1_b1_1 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b2 ab_a1_b2_2 (actual rows=1 loops=1) + -> Bitmap Heap Scan on ab_a1_b2 ab_a1_b2_1 (actual rows=1 loops=1) Recheck Cond: (a = 1) Heap Blocks: exact=1 -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_2 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_1 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) @@ -2580,16 +2580,16 @@ update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a; Index Cond: (a = 1) -> Nested Loop (actual rows=0 loops=1) -> Append (actual rows=1 loops=1) - -> Bitmap Heap Scan on ab_a1_b1 ab_a1_b1_3 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b1 ab_a1_b1_1 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b2 ab_a1_b2_3 (actual rows=1 loops=1) + -> Bitmap Heap Scan on ab_a1_b2 ab_a1_b2_1 (actual rows=1 loops=1) Recheck Cond: (a = 1) Heap Blocks: exact=1 -> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) - -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_3 (actual rows=0 loops=1) + -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_1 (actual rows=0 loops=1) Recheck Cond: (a = 1) -> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1) Index Cond: (a = 1) diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out index a6a499ed4a..2e170497c9 100644 --- a/src/test/regress/expected/rowsecurity.out +++ b/src/test/regress/expected/rowsecurity.out @@ -1815,26 +1815,26 @@ AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2; -> Seq Scan on t3 t1_2_2 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Nested Loop - Join Filter: (t1_1_1.b = t1_2_3.b) + Join Filter: (t1_1_1.b = t1_2.b) -> Seq Scan on t2 t1_1_1 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Append - -> Seq Scan on t1 t1_2_3 + -> Seq Scan on t1 t1_2 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t2 t1_2_4 + -> Seq Scan on t2 t1_2_1 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t3 t1_2_5 + -> Seq Scan on t3 t1_2_2 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Nested Loop - Join Filter: (t1_1_2.b = t1_2_6.b) + Join Filter: (t1_1_2.b = t1_2.b) -> Seq Scan on t3 t1_1_2 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -> Append - -> Seq Scan on t1 t1_2_6 + -> Seq Scan on t1 t1_2 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t2 t1_2_7 + -> Seq Scan on t2 t1_2_1 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t3 t1_2_8 + -> Seq Scan on t3 t1_2_2 Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) (37 rows) -- 2.11.0
v27-0004-Perform-pruning-in-expand_partitioned_rtentry.patch (text/plain, 28.7K) download From 96999a2316b4535addc2c7ef5b78c1774bb0c1ae Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 4 Mar 2019 11:43:38 +0900 Subject: [PATCH v27 4/6] Perform pruning in expand_partitioned_rtentry This allows to avoid opening/locking partitions that won't be scanned at all. Since expand_partitioned_rtentry recursivly processes sub-partitioned tables, translated quals must be added right when the child RelOptInfo is built for the sub-partitioned tables. So, build_simple_rel itself performs apply_child_basequals. Code for partitioning optimizations that access partition RelOptInfos from part_rels array of the parent's RelOptInfo must now be made aware that some entries might be NULL due partition pruning. In the case of partitionwise join, even a pruned partition may need to be joined if it falls on the nullable side of an outer join. A dummy RelOptInfo containing a dummy path is built in that case and put into its parent's part_rels array. --- src/backend/optimizer/path/allpaths.c | 176 +------------------------------ src/backend/optimizer/path/joinrels.c | 92 ++++++++++++++++- src/backend/optimizer/plan/planner.c | 8 ++ src/backend/optimizer/util/inherit.c | 21 +++- src/backend/optimizer/util/relnode.c | 188 +++++++++++++++++++++++++++++++--- src/backend/partitioning/partprune.c | 43 ++++---- 6 files changed, 320 insertions(+), 208 deletions(-) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 8d8a8f17d5..3191836c19 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -138,9 +138,6 @@ static void subquery_push_qual(Query *subquery, static void recurse_push_qual(Node *setOp, Query *topquery, RangeTblEntry *rte, Index rti, Node *qual); static void remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel); -static bool apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, - RelOptInfo *childrel, - RangeTblEntry *childRTE, AppendRelInfo *appinfo); /* @@ -945,8 +942,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, double *parent_attrsizes; int nattrs; ListCell *l; - Relids live_children = NULL; - bool did_pruning = false; /* Guard against stack overflow due to overly deep inheritance tree. */ check_stack_depth(); @@ -965,21 +960,6 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, rel->partitioned_child_rels = list_make1_int(rti); /* - * If the partitioned relation has any baserestrictinfo quals then we - * attempt to use these quals to prune away partitions that cannot - * possibly contain any tuples matching these quals. In this case we'll - * store the relids of all partitions which could possibly contain a - * matching tuple, and skip anything else in the loop below. - */ - if (enable_partition_pruning && - rte->relkind == RELKIND_PARTITIONED_TABLE && - rel->baserestrictinfo != NIL) - { - live_children = prune_append_rel_partitions(rel); - did_pruning = true; - } - - /* * If this is a partitioned baserel, set the consider_partitionwise_join * flag; currently, we only consider partitionwise joins with the baserel * if its targetlist doesn't contain a whole-row Var. @@ -1033,30 +1013,11 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); - if (did_pruning && !bms_is_member(appinfo->child_relid, live_children)) - { - /* This partition was pruned; skip it. */ - set_dummy_rel_pathlist(childrel); + /* build_simple_rel may have already proven the child to be dummy. */ + if (IS_DUMMY_REL(childrel)) continue; - } - - /* - * 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 - * baserestrictinfo quals. - */ - if (!apply_child_basequals(root, rel, childrel, childRTE, appinfo)) - { - /* - * Some restriction clause reduced to constant FALSE or NULL after - * substitution, so this child need not be scanned. - */ - set_dummy_rel_pathlist(childrel); - continue; - } + /* Apply constraint exclusion. */ if (relation_excluded_by_constraints(root, childrel, childRTE)) { /* @@ -1068,7 +1029,8 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, } /* - * CE failed, so finish copying/modifying targetlist and join quals. + * Constraint exclusion failed, so finish copying/modifying targetlist + * and join quals. * * NB: the resulting childrel->reltarget->exprs may contain arbitrary * expressions, which otherwise would not occur in a rel's targetlist. @@ -3557,134 +3519,6 @@ generate_partitionwise_join_paths(PlannerInfo *root, RelOptInfo *rel) list_free(live_children); } -/* - * apply_child_basequals - * Populate childrel's quals based on rel's quals, translating them using - * appinfo. - * - * If any of the resulting clauses evaluate to false or NULL, we return false - * and don't apply any quals. Caller can mark the relation as a dummy rel in - * this case, since it needn't be scanned. - * - * If any resulting clauses evaluate to true, they're unnecessary and we don't - * apply then. - */ -static bool -apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, - RelOptInfo *childrel, RangeTblEntry *childRTE, - AppendRelInfo *appinfo) -{ - List *childquals; - Index cq_min_security; - ListCell *lc; - - /* - * The child rel's targetlist might contain non-Var expressions, which - * means that substitution into the quals could produce opportunities for - * const-simplification, and perhaps even pseudoconstant quals. Therefore, - * transform each RestrictInfo separately to see if it reduces to a - * constant or pseudoconstant. (We must process them separately to keep - * track of the security level of each qual.) - */ - childquals = NIL; - cq_min_security = UINT_MAX; - foreach(lc, rel->baserestrictinfo) - { - RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); - Node *childqual; - ListCell *lc2; - - Assert(IsA(rinfo, RestrictInfo)); - childqual = adjust_appendrel_attrs(root, - (Node *) rinfo->clause, - 1, &appinfo); - childqual = eval_const_expressions(root, childqual); - /* check for flat-out constant */ - if (childqual && IsA(childqual, Const)) - { - if (((Const *) childqual)->constisnull || - !DatumGetBool(((Const *) childqual)->constvalue)) - { - /* Restriction reduces to constant FALSE or NULL */ - return false; - } - /* Restriction reduces to constant TRUE, so drop it */ - continue; - } - /* might have gotten an AND clause, if so flatten it */ - foreach(lc2, make_ands_implicit((Expr *) childqual)) - { - Node *onecq = (Node *) lfirst(lc2); - bool pseudoconstant; - - /* check for pseudoconstant (no Vars or volatile functions) */ - pseudoconstant = - !contain_vars_of_level(onecq, 0) && - !contain_volatile_functions(onecq); - if (pseudoconstant) - { - /* tell createplan.c to check for gating quals */ - root->hasPseudoConstantQuals = true; - } - /* reconstitute RestrictInfo with appropriate properties */ - childquals = lappend(childquals, - make_restrictinfo((Expr *) onecq, - rinfo->is_pushed_down, - rinfo->outerjoin_delayed, - pseudoconstant, - rinfo->security_level, - NULL, NULL, NULL)); - /* track minimum security level among child quals */ - cq_min_security = Min(cq_min_security, rinfo->security_level); - } - } - - /* - * In addition to the quals inherited from the parent, we might have - * securityQuals associated with this particular child node. (Currently - * this can only happen in appendrels originating from UNION ALL; - * inheritance child tables don't have their own securityQuals, see - * expand_inherited_rtentry().) Pull any such securityQuals up into the - * baserestrictinfo for the child. This is similar to - * process_security_barrier_quals() for the parent rel, except that we - * can't make any general deductions from such quals, since they don't - * hold for the whole appendrel. - */ - if (childRTE->securityQuals) - { - Index security_level = 0; - - foreach(lc, childRTE->securityQuals) - { - List *qualset = (List *) lfirst(lc); - ListCell *lc2; - - foreach(lc2, qualset) - { - Expr *qual = (Expr *) lfirst(lc2); - - /* not likely that we'd see constants here, so no check */ - childquals = lappend(childquals, - make_restrictinfo(qual, - true, false, false, - security_level, - NULL, NULL, NULL)); - cq_min_security = Min(cq_min_security, security_level); - } - security_level++; - } - Assert(security_level <= root->qual_security_level); - } - - /* - * OK, we've got all the baserestrictinfo quals for this child. - */ - childrel->baserestrictinfo = childquals; - childrel->baserestrict_min_security = cq_min_security; - - return true; -} - /***************************************************************************** * DEBUG SUPPORT *****************************************************************************/ diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index dfbbfdac6d..39dbe8bea3 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -14,6 +14,7 @@ */ #include "postgres.h" +#include "access/table.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" @@ -21,6 +22,8 @@ #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" +#include "optimizer/tlist.h" +#include "parser/parsetree.h" #include "partitioning/partbounds.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -52,6 +55,9 @@ static SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, Relids left_relids, Relids right_relids); static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op); +static RelOptInfo *build_dummy_partition_rel(PlannerInfo *root, + RelOptInfo *parent, Relation parentrel, + int partidx); /* @@ -1318,6 +1324,8 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, RelOptInfo *joinrel, SpecialJoinInfo *parent_sjinfo, List *parent_restrictlist) { + Relation baserel1 = NULL, + baserel2 = NULL; bool rel1_is_simple = IS_SIMPLE_REL(rel1); bool rel2_is_simple = IS_SIMPLE_REL(rel2); int nparts; @@ -1368,6 +1376,18 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, nparts = joinrel->nparts; + if (rel1_is_simple) + { + RangeTblEntry *rte = planner_rt_fetch(rel1->relid, root); + + baserel1 = table_open(rte->relid, NoLock); + } + if (rel2_is_simple) + { + RangeTblEntry *rte = planner_rt_fetch(rel2->relid, root); + + baserel2 = table_open(rte->relid, NoLock); + } /* * Create child-join relations for this partitioned join, if those don't * exist. Add paths to child-joins for a pair of child relations @@ -1384,6 +1404,13 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, AppendRelInfo **appinfos; int nappinfos; + if (rel1_is_simple && child_rel1 == NULL) + child_rel1 = build_dummy_partition_rel(root, rel1, baserel1, + cnt_parts); + if (rel2_is_simple && child_rel2 == NULL) + child_rel2 = build_dummy_partition_rel(root, rel2, baserel2, + cnt_parts); + /* * If a child table has consider_partitionwise_join=false, it means * that it's a dummy relation for which we skipped setting up tlist @@ -1444,6 +1471,11 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, child_joinrel, child_sjinfo, child_restrictlist); } + + if (baserel1) + table_close(baserel1, NoLock); + if (baserel2) + table_close(baserel2, NoLock); } /* @@ -1462,8 +1494,14 @@ update_child_rel_info(PlannerInfo *root, (Node *) rel->reltarget->exprs, 1, &appinfo); - /* Make child entries in the EquivalenceClass as well */ - if (rel->has_eclass_joins || has_useful_pathkeys(root, rel)) + /* + * Make child entries in the EquivalenceClass as well. If the childrel + * appears to be a dummy one (one built by build_dummy_partition_rel()), + * no need to make any new entries, because anything that would need those + * can instead use the parent's (rel). + */ + if (childrel->relid != rel->relid && + (rel->has_eclass_joins || has_useful_pathkeys(root, rel))) add_child_rel_equivalences(root, appinfo, rel, childrel); childrel->has_eclass_joins = rel->has_eclass_joins; } @@ -1674,3 +1712,53 @@ match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel, bool strict_op) return -1; } + +/* + * build_dummy_partition_rel + * Build a RelOptInfo and AppendRelInfo for a pruned partition + * + * This does not result in opening the relation or a range table entry being + * created. Also, the RelOptInfo thus created is not stored anywhere else + * beside the parent's part_rels array. + * + * The only reason this exists is because partition-wise join, in some cases, + * needs a RelOptInfo to represent an empty relation that's on the nullable + * side of an outer join, so that a Path representing the outer join can be + * created. + */ +static RelOptInfo * +build_dummy_partition_rel(PlannerInfo *root, RelOptInfo *parent, + Relation parentrel, int partidx) +{ + RelOptInfo *rel; + + Assert(parent->part_rels[partidx] == NULL); + + /* Create minimally valid-looking RelOptInfo with parent's relid. */ + rel = makeNode(RelOptInfo); + rel->reloptkind = RELOPT_OTHER_MEMBER_REL; + rel->relid = parent->relid; + rel->relids = bms_copy(parent->relids); + if (parent->top_parent_relids) + rel->top_parent_relids = parent->top_parent_relids; + else + rel->top_parent_relids = bms_copy(parent->relids); + rel->reltarget = copy_pathtarget(parent->reltarget); + parent->part_rels[partidx] = rel; + mark_dummy_rel(rel); + + /* + * Now we'll need a (no-op) AppendRelInfo for parent, because we're + * setting the dummy partition's relid to be same as the parent's. + */ + if (root->append_rel_array[parent->relid] == NULL) + { + AppendRelInfo *appinfo = make_append_rel_info(parentrel, parentrel, + parent->relid, + parent->relid); + + root->append_rel_array[parent->relid] = appinfo; + } + + return rel; +} diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index e3016d6a42..3da2a8eab2 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -7142,6 +7142,10 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; + /* Skip processing pruned partitions. */ + if (child_rel == NULL) + continue; + /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); @@ -7243,6 +7247,10 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; + /* Skip processing pruned partitions. */ + if (child_input_rel == NULL) + continue; + /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 697de74505..4e3b2fd7e7 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -26,6 +26,7 @@ #include "optimizer/planner.h" #include "optimizer/prep.h" #include "partitioning/partdesc.h" +#include "partitioning/partprune.h" #include "utils/rel.h" @@ -246,6 +247,7 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, PlanRowMark *top_parentrc, LOCKMODE lockmode, List **appinfos) { + Bitmapset *live_parts = 0; int i; RangeTblEntry *childrte; Index childRTindex; @@ -283,22 +285,34 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, rel = find_base_rel(root, parentRTindex); /* + * Perform partition pruning using restriction clauses assigned to + * parent relation. live_parts will contain PartitionDesc indexes + * of partitions that survive pruning. Below, we will initialize + * child objects for the surviving partitions. + */ + if (partdesc->nparts > 0) + live_parts = prune_append_rel_partitions(rel); + + /* * Expand simple_rel_array and friends to hold child objects. * * We'll need nparts + 1 new slots, because we also consider parent * as a child relation. FIXME: no longer add the parent as child */ - expand_planner_arrays(root, partdesc->nparts + 1); + expand_planner_arrays(root, bms_num_members(live_parts) + 1); /* * We also store partition RelOptInfo pointers in the parent - * relation. + * relation. Since we're palloc0'ing, slots corresponding to + * pruned partitions will contain NULL. */ Assert(rel->part_rels == NULL); if (partdesc->nparts > 0) rel->part_rels = (RelOptInfo **) palloc0(rel->nparts * sizeof(RelOptInfo *)); } + else + live_parts = bms_add_range(NULL, 0, partdesc->nparts - 1); /* First expand the partitioned table itself. */ expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, @@ -315,7 +329,8 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, return; } - for (i = 0; i < partdesc->nparts; i++) + i = -1; + while ((i = bms_next_member(live_parts, i)) >= 0) { Oid childOID = partdesc->oids[i]; Relation childrel; diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 6d960582f3..1937d0442c 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -70,6 +70,9 @@ static void build_child_join_reltarget(PlannerInfo *root, RelOptInfo *childrel, int nappinfos, AppendRelInfo **appinfos); +static bool apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *childrel, + RangeTblEntry *childRTE, AppendRelInfo *appinfo); /* @@ -324,10 +327,162 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) root->qual_security_level = Max(root->qual_security_level, list_length(rte->securityQuals)); + /* + * Copy the parent's quals to the child, with appropriate substitution of + * variables. If any constant false or NULL clauses turn up, we can + * disregard the child as dummy right away. + * + * We must copy the quals now, because if the child rel happens to be a + * partitioned table, expand_partitioned_rtentry (our caller) can + * immediate perform partition prunning using the translated quals. + */ + if (parent) + { + AppendRelInfo *appinfo = root->append_rel_array[relid]; + + Assert(appinfo != NULL); + if (!apply_child_basequals(root, parent, rel, rte, appinfo)) + { + /* + * Some restriction clause reduced to constant FALSE or NULL after + * substitution, so this child need not be scanned. + */ + set_dummy_rel_pathlist(rel); + } + } + return rel; } /* + * apply_child_basequals + * Populate childrel's quals based on rel's quals, translating them using + * appinfo. + * + * If any of the resulting clauses evaluate to false or NULL, we return false + * and don't apply any quals. Caller can mark the relation as a dummy rel in + * this case, since it doesn't need to be scanned. + * + * If any resulting clauses evaluate to true, they're unnecessary and we don't + * apply then. + */ +static bool +apply_child_basequals(PlannerInfo *root, RelOptInfo *rel, + RelOptInfo *childrel, RangeTblEntry *childRTE, + AppendRelInfo *appinfo) +{ + List *childquals; + Index cq_min_security; + ListCell *lc; + + /* + * The child rel's targetlist might contain non-Var expressions, which + * means that substitution into the quals could produce opportunities for + * const-simplification, and perhaps even pseudoconstant quals. Therefore, + * transform each RestrictInfo separately to see if it reduces to a + * constant or pseudoconstant. (We must process them separately to keep + * track of the security level of each qual.) + */ + childquals = NIL; + cq_min_security = UINT_MAX; + foreach(lc, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Node *childqual; + ListCell *lc2; + + Assert(IsA(rinfo, RestrictInfo)); + childqual = adjust_appendrel_attrs(root, + (Node *) rinfo->clause, + 1, &appinfo); + childqual = eval_const_expressions(root, childqual); + /* check for flat-out constant */ + if (childqual && IsA(childqual, Const)) + { + if (((Const *) childqual)->constisnull || + !DatumGetBool(((Const *) childqual)->constvalue)) + { + /* Restriction reduces to constant FALSE or NULL */ + return false; + } + /* Restriction reduces to constant TRUE, so drop it */ + continue; + } + /* might have gotten an AND clause, if so flatten it */ + foreach(lc2, make_ands_implicit((Expr *) childqual)) + { + Node *onecq = (Node *) lfirst(lc2); + bool pseudoconstant; + + /* check for pseudoconstant (no Vars or volatile functions) */ + pseudoconstant = + !contain_vars_of_level(onecq, 0) && + !contain_volatile_functions(onecq); + if (pseudoconstant) + { + /* tell createplan.c to check for gating quals */ + root->hasPseudoConstantQuals = true; + } + /* reconstitute RestrictInfo with appropriate properties */ + childquals = lappend(childquals, + make_restrictinfo((Expr *) onecq, + rinfo->is_pushed_down, + rinfo->outerjoin_delayed, + pseudoconstant, + rinfo->security_level, + NULL, NULL, NULL)); + /* track minimum security level among child quals */ + cq_min_security = Min(cq_min_security, rinfo->security_level); + } + } + + /* + * In addition to the quals inherited from the parent, we might have + * securityQuals associated with this particular child node. (Currently + * this can only happen in appendrels originating from UNION ALL; + * inheritance child tables don't have their own securityQuals, see + * expand_inherited_rtentry().) Pull any such securityQuals up into the + * baserestrictinfo for the child. This is similar to + * process_security_barrier_quals() for the parent rel, except that we + * can't make any general deductions from such quals, since they don't + * hold for the whole appendrel. + */ + if (childRTE->securityQuals) + { + Index security_level = 0; + + foreach(lc, childRTE->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Expr *qual = (Expr *) lfirst(lc2); + + /* not likely that we'd see constants here, so no check */ + childquals = lappend(childquals, + make_restrictinfo(qual, + true, false, false, + security_level, + NULL, NULL, NULL)); + cq_min_security = Min(cq_min_security, security_level); + } + security_level++; + } + Assert(security_level <= root->qual_security_level); + } + + /* + * OK, we've got all the baserestrictinfo quals for this child. + */ + childrel->baserestrictinfo = childquals; + childrel->baserestrict_min_security = cq_min_security; + + return true; +} + +/* * add_appendrel_other_rels * This adds the "other rel" RelOptInfos a given "appendrel" baserel * @@ -339,6 +494,8 @@ add_appendrel_other_rels(PlannerInfo *root, RangeTblEntry *rte, Index rti) { ListCell *l; RelOptInfo *rel; + Relation relation = NULL; + PartitionDesc partdesc = NULL; int i; /* @@ -362,16 +519,19 @@ add_appendrel_other_rels(PlannerInfo *root, RangeTblEntry *rte, Index rti) /* * For partitioned tables, we need to store the child RelOptInfos in the - * rel->part_rels array too. + * rel->part_rels array too. Some elements of this array might remain set + * to NULL if the corresponding partitions were pruned and hence not in + * append_rel_list. */ if (rel->part_scheme) { Assert(rel->nparts > 0); rel->part_rels = (RelOptInfo **) palloc0(sizeof(RelOptInfo *) * rel->nparts); + relation = table_open(rte->relid, NoLock); + partdesc = RelationGetPartitionDesc(relation); } - i = 0; foreach(l, root->append_rel_list) { AppendRelInfo *appinfo = lfirst(l); @@ -391,26 +551,30 @@ add_appendrel_other_rels(PlannerInfo *root, RangeTblEntry *rte, Index rti) * For partitioned parents, we also need to add childrel to its * part_rels array. The order in which child tables appear in * append_rel_list is same as the order in which they appear in the - * parent's PartitionDesc, so assigning partitions like this works. + * parent's PartitionDesc. But considering that some partitions may + * have been pruned, we need to look up the correct position to + * assign this childrel by scanning the PartitionDesc. */ if (rel->part_scheme != NULL) { - Assert(i < rel->nparts); - rel->part_rels[i] = childrel; + Assert(partdesc != NULL); + for (i = 0; i < partdesc->nparts; i++) + { + if (childrte->relid == partdesc->oids[i]) + { + rel->part_rels[i] = childrel; + break; + } + } } - i++; - /* Child may itself be an inherited relation. */ if (childrte->inh) add_appendrel_other_rels(root, childrte, childRTindex); } - /* - * For a partitioned table with non-zero number of partitions, we must - * have assigned all elements of its part_rels array. - */ - Assert(rel->nparts == 0 || i == rel->nparts); + if (relation) + table_close(relation, NoLock); } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 8c9721935d..30ebce4e40 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -45,6 +45,7 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/cost.h" #include "optimizer/optimizer.h" #include "optimizer/pathnode.h" #include "partitioning/partprune.h" @@ -433,17 +434,23 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, * is, not pruned already). */ subplan_map = (int *) palloc(nparts * sizeof(int)); + memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); + memset(subpart_map, -1, nparts * sizeof(int)); present_parts = NULL; for (i = 0; i < nparts; i++) { RelOptInfo *partrel = subpart->part_rels[i]; - int subplanidx = relid_subplan_map[partrel->relid] - 1; - int subpartidx = relid_subpart_map[partrel->relid] - 1; + int subplanidx; + int subpartidx; - subplan_map[i] = subplanidx; - subpart_map[i] = subpartidx; + /* Skip processing pruned partitions. */ + if (partrel == NULL) + continue; + + subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; + subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; if (subplanidx >= 0) { present_parts = bms_add_member(present_parts, i); @@ -537,23 +544,20 @@ gen_partprune_steps(RelOptInfo *rel, List *clauses, bool *contradictory) /* * prune_append_rel_partitions - * Returns RT indexes of the minimum set of child partitions which must - * be scanned to satisfy rel's baserestrictinfo quals. + * Returns indexes into rel->part_rels of the minimum set of child + * partitions which must be scanned to satisfy rel's baserestrictinfo + * quals. * * Callers must ensure that 'rel' is a partitioned table. */ -Relids +Bitmapset * prune_append_rel_partitions(RelOptInfo *rel) { - Relids result; List *clauses = rel->baserestrictinfo; List *pruning_steps; bool contradictory; PartitionPruneContext context; - Bitmapset *partindexes; - int i; - Assert(clauses != NIL); Assert(rel->part_scheme != NULL); /* If there are no partitions, return the empty set */ @@ -561,6 +565,13 @@ prune_append_rel_partitions(RelOptInfo *rel) return NULL; /* + * If pruning is disabled or if there are no clauses to prune with, + * return all partitions. + */ + if (!enable_partition_pruning || clauses == NIL) + return bms_add_range(NULL, 0, rel->nparts - 1); + + /* * Process clauses. If the clauses are found to be contradictory, we can * return the empty set. */ @@ -587,15 +598,7 @@ prune_append_rel_partitions(RelOptInfo *rel) context.evalexecparams = false; /* Actual pruning happens here. */ - partindexes = get_matching_partitions(&context, pruning_steps); - - /* Add selected partitions' RT indexes to result. */ - i = -1; - result = NULL; - while ((i = bms_next_member(partindexes, i)) >= 0) - result = bms_add_member(result, rel->part_rels[i]->relid); - - return result; + return get_matching_partitions(&context, pruning_steps); } /* -- 2.11.0
v27-0005-Teach-planner-to-only-process-unpruned-partition.patch (text/plain, 7.5K) download From 8606dd19470bd956c211071a1ad6d77f0523bea0 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 4 Mar 2019 11:57:04 +0900 Subject: [PATCH v27 5/6] Teach planner to only process unpruned partitions This adds a bitmapset field live_parts to RelOptInfo and stores the partition indexes of non-dummy partitions in it, meaning it contains indexes of only those partitions that have a non-NULL RelOptInfo present in its parent's RelOptInfo's part_rels array. This speeds up processing partitioned table's partitions compared to going through the whole part_rels array in a number of places. Partitionwise join still has to visit each member of part_rels, because pruned children may fall on the nullable side of an outer join and an empty Result path must be present for building the outer join paths. --- src/backend/optimizer/path/joinrels.c | 3 +++ src/backend/optimizer/plan/planner.c | 18 +++++++++--------- src/backend/optimizer/util/inherit.c | 6 ++++++ src/backend/optimizer/util/relnode.c | 4 ++++ src/backend/partitioning/partprune.c | 18 +++++------------- src/include/nodes/pathnodes.h | 4 ++++ 6 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 39dbe8bea3..2a07fa71d5 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -1470,6 +1470,9 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, populate_joinrel_with_paths(root, child_rel1, child_rel2, child_joinrel, child_sjinfo, child_restrictlist); + if (!IS_DUMMY_REL(child_joinrel)) + joinrel->live_parts = bms_add_member(joinrel->live_parts, + cnt_parts); } if (baserel1) diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 3da2a8eab2..eaa5e18c8e 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -7134,7 +7134,9 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, List *live_children = NIL; /* Adjust each partition. */ - for (partition_idx = 0; partition_idx < rel->nparts; partition_idx++) + partition_idx = -1; + while ((partition_idx = bms_next_member(rel->live_parts, + partition_idx)) >= 0) { RelOptInfo *child_rel = rel->part_rels[partition_idx]; ListCell *lc; @@ -7142,9 +7144,7 @@ apply_scanjoin_target_to_paths(PlannerInfo *root, int nappinfos; List *child_scanjoin_targets = NIL; - /* Skip processing pruned partitions. */ - if (child_rel == NULL) - continue; + Assert(child_rel != NULL); /* Translate scan/join targets for this child. */ appinfos = find_appinfos_by_relids(root, child_rel->relids, @@ -7225,7 +7225,6 @@ create_partitionwise_grouping_paths(PlannerInfo *root, PartitionwiseAggregateType patype, GroupPathExtraData *extra) { - int nparts = input_rel->nparts; int cnt_parts; List *grouped_live_children = NIL; List *partially_grouped_live_children = NIL; @@ -7237,7 +7236,9 @@ create_partitionwise_grouping_paths(PlannerInfo *root, partially_grouped_rel != NULL); /* Add paths for partitionwise aggregation/grouping. */ - for (cnt_parts = 0; cnt_parts < nparts; cnt_parts++) + cnt_parts = -1; + while ((cnt_parts = bms_next_member(input_rel->live_parts, + cnt_parts)) >= 0) { RelOptInfo *child_input_rel = input_rel->part_rels[cnt_parts]; PathTarget *child_target = copy_pathtarget(target); @@ -7247,9 +7248,8 @@ create_partitionwise_grouping_paths(PlannerInfo *root, RelOptInfo *child_grouped_rel; RelOptInfo *child_partially_grouped_rel; - /* Skip processing pruned partitions. */ - if (child_input_rel == NULL) - continue; + /* A live partition must have a RelOptInfo. */ + Assert(child_input_rel != NULL); /* Input child rel must have a path */ Assert(child_input_rel->pathlist != NIL); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 4e3b2fd7e7..363129de01 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -294,6 +294,12 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, live_parts = prune_append_rel_partitions(rel); /* + * Later steps that loop over part_rels should use these indexes + * to access unpruned partitions. + */ + rel->live_parts = live_parts; + + /* * Expand simple_rel_array and friends to hold child objects. * * We'll need nparts + 1 new slots, because we also consider parent diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 1937d0442c..0065dd6ca2 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -563,6 +563,7 @@ add_appendrel_other_rels(PlannerInfo *root, RangeTblEntry *rte, Index rti) if (childrte->relid == partdesc->oids[i]) { rel->part_rels[i] = childrel; + rel->live_parts = bms_add_member(rel->live_parts, i); break; } } @@ -1989,6 +1990,9 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, joinrel->partexprs[cnt] = partexpr; joinrel->nullable_partexprs[cnt] = nullable_partexpr; } + + /* Partitions will be added by try_partitionwise_join. */ + joinrel->live_parts = NULL; } /* diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c index 30ebce4e40..0f990ef6a2 100644 --- a/src/backend/partitioning/partprune.c +++ b/src/backend/partitioning/partprune.c @@ -437,29 +437,21 @@ make_partitionedrel_pruneinfo(PlannerInfo *root, RelOptInfo *parentrel, memset(subplan_map, -1, nparts * sizeof(int)); subpart_map = (int *) palloc(nparts * sizeof(int)); memset(subpart_map, -1, nparts * sizeof(int)); - present_parts = NULL; + present_parts = bms_copy(subpart->live_parts); - for (i = 0; i < nparts; i++) + i = -1; + while ((i = bms_next_member(present_parts, i)) >= 0) { RelOptInfo *partrel = subpart->part_rels[i]; int subplanidx; int subpartidx; - /* Skip processing pruned partitions. */ - if (partrel == NULL) - continue; - + Assert(partrel != NULL); subplan_map[i] = subplanidx = relid_subplan_map[partrel->relid] - 1; subpart_map[i] = subpartidx = relid_subpart_map[partrel->relid] - 1; + /* Record finding this subplan */ if (subplanidx >= 0) - { - present_parts = bms_add_member(present_parts, i); - - /* Record finding this subplan */ subplansfound = bms_add_member(subplansfound, subplanidx); - } - else if (subpartidx >= 0) - present_parts = bms_add_member(present_parts, i); } pinfo = makeNode(PartitionedRelPruneInfo); diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index c2e292e54c..53d0485964 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -712,6 +712,10 @@ typedef struct RelOptInfo List *partition_qual; /* partition constraint */ struct RelOptInfo **part_rels; /* Array of RelOptInfos of partitions, * stored in the same order of bounds */ + Bitmapset *live_parts; /* Indexes into part_rels of the non-NULL + * RelOptInfos of unpruned partitions; exists + * to avoid having to iterate over the entire + * part_rels array to filter NULL entries. */ List **partexprs; /* Non-nullable partition key expressions. */ List **nullable_partexprs; /* Nullable partition key expressions. */ List *partitioned_child_rels; /* List of RT indexes. */ -- 2.11.0
v27-0006-Don-t-copy-PartitionBoundInfo-in-set_relation_pa.patch (text/plain, 1.6K) download From 791faa4f6e1ef1ba5b5e1963aaaacd68c02acc71 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Mon, 4 Mar 2019 16:03:49 +0900 Subject: [PATCH v27 6/6] Don't copy PartitionBoundInfo in set_relation_partition_info As long as we hold a lock on the table, it shouldn't change, so seems pointless to copy, which can get really expensive as the number of partitions grows beyond thousands. --- src/backend/optimizer/util/plancat.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 549af9dc6e..b75b761033 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -2082,16 +2082,18 @@ set_relation_partition_info(PlannerInfo *root, RelOptInfo *rel, Relation relation) { PartitionDesc partdesc; - PartitionKey partkey; Assert(relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); partdesc = RelationGetPartitionDesc(relation); - partkey = RelationGetPartitionKey(relation); rel->part_scheme = find_partition_scheme(root, relation); Assert(partdesc != NULL && rel->part_scheme != NULL); + /* + * Holding onto the relcache pointer should be OK, as it won't change + * under us as long as we're holding the lock on the relation. + */ if (partdesc->nparts > 0) - rel->boundinfo = partition_bounds_copy(partdesc->boundinfo, partkey); + rel->boundinfo = partdesc->boundinfo; rel->nparts = partdesc->nparts; set_baserel_partition_key_exprs(relation, rel); rel->partition_qual = RelationGetPartitionQual(relation); -- 2.11.0
^ permalink raw reply [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-03-05 10:25  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  1 sibling, 1 reply; 265+ messages in thread

From: Amit Langote @ 2019-03-05 10:25 UTC (permalink / raw)
  To: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; +Cc: 'Amit Langote' <amitlangote09@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Justin Pryzby <pryzby@telsasoft.com>; pgsql-hackers

On 2019/03/04 19:38, Amit Langote wrote:
> 2. Defer inheritance expansion to add_other_rels_to_query().  Although the
> purpose of doing this is to perform partition pruning before adding the
> children, this patch doesn't change when the pruning occurs.  It deals
> with other issues that must be taken care of due to adding children during
> query_planner instead of during subquery_planner.  Especially,
> inheritance_planner now has to add the child target relations on its own.
> Also, delaying adding children also affects adding junk columns to the
> query's targetlist based on PlanRowMarks, because preprocess_targetlist
> can no longer finalize which junk columns to add for a "parent"
> PlanRowMark; that must be delayed until all child PlanRowMarks are added
> and their allMarkTypes propagated to the parent PlanRowMark.

I thought more on this and started wondering why we can't call
preprocess_targetlist() from query_planner() instead of from
grouping_planner()?  We don't have to treat parent row marks specially if
preprocess_targetlist() is called after adding other rels (and hence all
child row marks).  This will change the order in which expressions are
added to baserels targetlists and hence the order of expressions in their
Path's targetlist, because the expressions contained in targetlist
(including RETURNING) and other junk expressions will be added after
expressions referenced in WHERE clauses, whereas the order is reverse
today.  But if we do what we propose above, the order will be uniform for
all cases, that is, not one for regular table baserels and another for
inherited table baserels.

Thoughts?

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-03-05 15:57  Jesper Pedersen <jesper.pedersen@redhat.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  2 siblings, 1 reply; 265+ messages in thread

From: Jesper Pedersen @ 2019-03-05 15:57 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Amit Langote <amitlangote09@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Justin Pryzby <pryzby@telsasoft.com>; pgsql-hackers

On 3/5/19 5:24 AM, Amit Langote wrote:
> Attached an updated version.  This incorporates fixes for both Jesper's
> and Imai-san's review.  I haven't been able to pin down the bug (or
> whatever) that makes throughput go down as the partition count increases,
> when tested with a --enable-cassert build.
> 

Thanks !

I'm seeing the throughput going down as well, but are you sure it isn't 
just the extra calls of MemoryContextCheck you are seeing ? A flamegraph 
diff highlights that area -- sent offline.

A non cassert build shows the same profile for 64 and 1024 partitions.

Best regards,
  Jesper




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-03-06 02:00  Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  parent: Jesper Pedersen <jesper.pedersen@redhat.com>
  0 siblings, 0 replies; 265+ messages in thread

From: Amit Langote @ 2019-03-06 02:00 UTC (permalink / raw)
  To: jesper.pedersen@redhat.com; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; 'Amit Langote' <amitlangote09@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Justin Pryzby <pryzby@telsasoft.com>; pgsql-hackers

On 2019/03/06 0:57, Jesper Pedersen wrote:
> On 3/5/19 5:24 AM, Amit Langote wrote:
>> Attached an updated version.  This incorporates fixes for both Jesper's
>> and Imai-san's review.  I haven't been able to pin down the bug (or
>> whatever) that makes throughput go down as the partition count increases,
>> when tested with a --enable-cassert build.
>>
> 
> Thanks !
> 
> I'm seeing the throughput going down as well, but are you sure it isn't
> just the extra calls of MemoryContextCheck you are seeing ? A flamegraph
> diff highlights that area -- sent offline.
> 
> A non cassert build shows the same profile for 64 and 1024 partitions.

Thanks for testing.

I now see what's happening here.  I was aware that it's MemoryContextCheck
overhead but didn't quite understand why the time it takes should increase
with the number of partitions increasing, especially given that the patch
makes it so that only one partition is accessed if that's what the query
needs to do.  What I had forgotten however is that MemoryContextCheck
checks *all* contexts in every transaction, including the "partition
descriptor" context which stores a partitioned table's PartitionDesc.
PartitionDesc gets bigger as the number of partitions increase, so does
the time to check the context it's allocated in.

So, the decrease in throughput in cassert build as the number of
partitions increases is not due to any fault of this patch series as I had
suspected.  Phew!

Thanks,
Amit





^ permalink  raw  reply  [nested|flat] 265+ messages in thread

* Re: speeding up planning with partitions
@ 2019-03-07 17:21  Jesper Pedersen <jesper.pedersen@redhat.com>
  parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
  2 siblings, 1 reply; 265+ messages in thread

From: Jesper Pedersen @ 2019-03-07 17:21 UTC (permalink / raw)
  To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>; Amit Langote <amitlangote09@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; David Rowley <david.rowley@2ndquadrant.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Justin Pryzby <pryzby@telsasoft.com>; pgsql-hackers

Hi,

On 3/5/19 5:24 AM, Amit Langote wrote:
> Attached an updated version. 

Need a rebase due to 898e5e3290.

Best regards,
  Jesper




^ permalink  raw  reply  [nested|flat] 265+ messages in thread

105 further messages in this thread omitted from this page (render size limit).
Use the per-message pages, or the t.mbox.gz byte stream, for the complete thread.


end of thread, other threads:[~2019-05-02 09:33 UTC | newest]

Thread overview: 265+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2018-08-29 12:06 speeding up planning with partitions Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-08-29 22:27 ` David Rowley <david.rowley@2ndquadrant.com>
2018-08-30 01:10   ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-08-30 01:09 ` Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
2018-08-30 01:33   ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-08-30 01:45     ` Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
2018-08-30 09:29 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-09-10 23:23   ` David Rowley <david.rowley@2ndquadrant.com>
2018-09-14 10:28     ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-09-29 13:30       ` Dilip Kumar <dilipbalaut@gmail.com>
2018-10-02 01:20         ` Michael Paquier <michael@paquier.xyz>
2018-10-02 01:24           ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-10-15 09:24         ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-10-02 23:31       ` Thomas Munro <thomas.munro@enterprisedb.com>
2018-10-03 00:47         ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-10-04 08:11       ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2018-10-04 09:06         ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-10-18 02:15         ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-10-26 10:37           ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-11-05 08:28             ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2018-11-07 01:00               ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2018-11-09 08:26                 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-11-12 04:35                   ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2018-11-09 08:04               ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-11-07 03:32             ` Alvaro Herrera <alvherre@2ndquadrant.com>
2018-11-07 03:44               ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-11-09 08:55                 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-11-09 17:43                   ` Jesper Pedersen <jesper.pedersen@redhat.com>
2018-11-10 11:59                   ` David Rowley <david.rowley@2ndquadrant.com>
2018-11-14 10:28                     ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-11-14 11:01                       ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-11-15 01:19                         ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2018-11-15 01:24                           ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-11-21 10:23                       ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-12-05 02:29                         ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2018-12-06 08:25                           ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-12-07 00:57                             ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2018-12-25 07:47                               ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2018-12-26 04:28                                 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-12-27 11:25                                   ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-01-03 15:39                                     ` Justin Pryzby <pryzby@telsasoft.com>
2019-01-07 08:40                                       ` David Rowley <david.rowley@2ndquadrant.com>
2019-01-07 09:17                                         ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-01-07 14:13                                         ` Justin Pryzby <pryzby@telsasoft.com>
2019-01-08 01:34                                           ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-01-08 06:30                                     ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-01-09 00:09                                       ` David Rowley <david.rowley@2ndquadrant.com>
2019-01-11 13:00                                         ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-01-12 08:49                                           ` David Rowley <david.rowley@2ndquadrant.com>
2019-01-15 05:54                                             ` David Rowley <david.rowley@2ndquadrant.com>
2019-01-16 06:22                                             ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-01-28 04:18                                           ` David Rowley <david.rowley@2ndquadrant.com>
2019-01-28 08:21                                             ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-01-29 02:23                                               ` David Rowley <david.rowley@2ndquadrant.com>
2019-01-29 09:32                                                 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-01-30 02:20                                                   ` David Rowley <david.rowley@2ndquadrant.com>
2019-01-30 17:28                                                     ` Amit Langote <amitlangote09@gmail.com>
2019-01-30 19:32                                                   ` David Rowley <david.rowley@2ndquadrant.com>
2019-02-01 09:51                                                     ` Alvaro Herrera <alvherre@2ndquadrant.com>
2019-02-01 14:09                                                       ` David Rowley <david.rowley@2ndquadrant.com>
2019-02-01 10:01                                                   ` Alvaro Herrera <alvherre@2ndquadrant.com>
2019-02-01 14:11                                                     ` David Rowley <david.rowley@2ndquadrant.com>
2019-02-02 13:52                                                       ` Amit Langote <amitlangote09@gmail.com>
2019-02-07 10:21                                                         ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-02-08 04:44                                                           ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-02-08 09:12                                                             ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-02-08 09:27                                                               ` David Rowley <david.rowley@2ndquadrant.com>
2019-02-08 12:22                                                                 ` David Rowley <david.rowley@2ndquadrant.com>
2019-02-12 06:41                                                                 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-02-18 17:27                                                                   ` Tom Lane <tgl@sss.pgh.pa.us>
2019-02-19 00:49                                                                     ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-02-19 01:36                                                                       ` Tom Lane <tgl@sss.pgh.pa.us>
2019-02-19 00:54                                                                     ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-02-18 19:42                                                                   ` Tom Lane <tgl@sss.pgh.pa.us>
2019-02-19 10:42                                                                     ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-02-19 17:07                                                                       ` Tom Lane <tgl@sss.pgh.pa.us>
2019-02-19 20:57                                                                         ` Tom Lane <tgl@sss.pgh.pa.us>
2019-02-20 05:47                                                                           ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-02-20 15:50                                                                             ` Tom Lane <tgl@sss.pgh.pa.us>
2019-02-21 02:31                                                                               ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-02-22 12:45                                                                                 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-02-22 16:59                                                                                   ` Justin Pryzby <pryzby@telsasoft.com>
2019-02-22 17:54                                                                                     ` Amit Langote <amitlangote09@gmail.com>
2019-02-22 18:01                                                                                       ` Justin Pryzby <pryzby@telsasoft.com>
2019-02-25 06:24                                                                                       ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-03-01 13:01                                                                                         ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-01 13:03                                                                                           ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-04 09:14                                                                                           ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-03-04 10:42                                                                                             ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-04 10:38                                                                                           ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-04 16:20                                                                                             ` Jesper Pedersen <jesper.pedersen@redhat.com>
2019-03-05 00:50                                                                                               ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-05 06:03                                                                                                 ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-03-05 07:16                                                                                                   ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-05 10:24                                                                                                 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-05 15:57                                                                                                   ` Jesper Pedersen <jesper.pedersen@redhat.com>
2019-03-06 02:00                                                                                                     ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-06 02:09                                                                                                   ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-03-06 02:14                                                                                                     ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-03-06 05:37                                                                                                     ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-07 00:59                                                                                                       ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-03-08 07:16                                                                                                       ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-03-08 09:18                                                                                                         ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-11 01:10                                                                                                           ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-03-20 00:49                                                                                                           ` Robert Haas <robertmhaas@gmail.com>
2019-03-20 00:57                                                                                                             ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-07 17:21                                                                                                   ` Jesper Pedersen <jesper.pedersen@redhat.com>
2019-03-08 06:26                                                                                                     ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-05 10:25                                                                                             ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-12 08:22                                                                                               ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-12 13:23                                                                                                 ` Jesper Pedersen <jesper.pedersen@redhat.com>
2019-03-12 14:33                                                                                                   ` Amit Langote <amitlangote09@gmail.com>
2019-03-13 10:34                                                                                                     ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-03-14 08:35                                                                                                       ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-03-14 09:04                                                                                                         ` David Rowley <david.rowley@2ndquadrant.com>
2019-03-15 01:38                                                                                                           ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-03-14 09:03                                                                                                       ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-15 00:33                                                                                                         ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-03-15 05:40                                                                                                           ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-03-18 09:56                                                                                                             ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-19 01:18                                                                                                               ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-03-19 06:53                                                                                                           ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-19 11:13                                                                                                             ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-03-20 00:41                                                                                                               ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-20 02:21                                                                                                                 ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-03-20 02:33                                                                                                                   ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-20 02:51                                                                                                                     ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-03-20 03:01                                                                                                                       ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-20 03:15                                                                                                                         ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-03-20 08:20                                                                                                                           ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-20 08:36                                                                                                                             ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-03-20 09:06                                                                                                                               ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-20 09:40                                                                                                                                 ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-03-20 14:25                                                                                                                           ` Jesper Pedersen <jesper.pedersen@redhat.com>
2019-03-21 14:10                                                                                                                             ` Imai Yoshikazu <yoshikazu_i443@live.jp>
2019-03-22 02:17                                                                                                                               ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-22 07:39                                                                                                                                 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-22 12:14                                                                                                                                   ` Jesper Pedersen <jesper.pedersen@redhat.com>
2019-03-22 13:34                                                                                                                                   ` David Rowley <david.rowley@2ndquadrant.com>
2019-03-22 21:07                                                                                                                                 ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-22 21:51                                                                                                                                   ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-25 01:47                                                                                                                                   ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-03-25 11:34                                                                                                                                   ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-26 07:16                                                                                                                                     ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-26 10:02                                                                                                                                       ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-03-26 16:06                                                                                                                                   ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-27 00:22                                                                                                                                     ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-26 22:26                                                                                                                                   ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-27 03:06                                                                                                                                     ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-27 04:50                                                                                                                                       ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-27 05:13                                                                                                                                         ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-27 05:26                                                                                                                                           ` David Rowley <david.rowley@2ndquadrant.com>
2019-03-27 05:39                                                                                                                                             ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-27 05:49                                                                                                                                               ` David Rowley <david.rowley@2ndquadrant.com>
2019-03-27 14:57                                                                                                                                                 ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-28 04:57                                                                                                                                                   ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-28 05:03                                                                                                                                                   ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-28 10:16                                                                                                                                                     ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-28 22:38                                                                                                                                                     ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-29 03:44                                                                                                                                                       ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-29 07:00                                                                                                                                                         ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-03-29 09:08                                                                                                                                                         ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-29 15:29                                                                                                                                                         ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-29 16:31                                                                                                                                                           ` Tom Lane <tgl@sss.pgh.pa.us>
2019-04-01 10:02                                                                                                                                                           ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-04-01 17:34                                                                                                                                                           ` Tom Lane <tgl@sss.pgh.pa.us>
2019-04-02 05:50                                                                                                                                                             ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-04-04 18:33                                                                                                                                                               ` Floris Van Nee <florisvannee@Optiver.com>
2019-04-04 21:59                                                                                                                                                                 ` David Rowley <david.rowley@2ndquadrant.com>
2019-04-05 03:09                                                                                                                                                                   ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-04-05 03:18                                                                                                                                                                     ` David Rowley <david.rowley@2ndquadrant.com>
2019-04-05 03:25                                                                                                                                                                       ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-04-05 09:13                                                                                                                                                                         ` Floris Van Nee <florisvannee@Optiver.com>
2019-04-05 10:07                                                                                                                                                                           ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-04-05 10:47                                                                                                                                                                             ` David Rowley <david.rowley@2ndquadrant.com>
2019-04-05 06:50                                                                                                                                                               ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-04-11 05:03                                                                                                                                                                 ` David Rowley <david.rowley@2ndquadrant.com>
2019-04-11 05:09                                                                                                                                                                   ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-04-22 22:08                                                                                                                                                               ` Tom Lane <tgl@sss.pgh.pa.us>
2019-04-23 04:26                                                                                                                                                                 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-04-27 23:10                                                                                                                                                                   ` Tom Lane <tgl@sss.pgh.pa.us>
2019-04-28 15:26                                                                                                                                                                     ` Amit Langote <amitlangote09@gmail.com>
2019-04-29 21:12                                                                                                                                                                       ` Tom Lane <tgl@sss.pgh.pa.us>
2019-04-30 19:08                                                                                                                                                                         ` Tom Lane <tgl@sss.pgh.pa.us>
2019-05-02 09:33                                                                                                                                                                           ` Amit Langote <amitlangote09@gmail.com>
2019-03-29 18:32                                                                                                                                                       ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-30 00:17                                                                                                                                                       ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-30 02:19                                                                                                                                                         ` Amit Langote <amitlangote09@gmail.com>
2019-03-30 15:11                                                                                                                                                           ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-30 15:36                                                                                                                                                             ` Robert Haas <robertmhaas@gmail.com>
2019-03-30 15:46                                                                                                                                                             ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-30 15:59                                                                                                                                                               ` Robert Haas <robertmhaas@gmail.com>
2019-03-30 16:16                                                                                                                                                                 ` Amit Langote <amitlangote09@gmail.com>
2019-03-30 16:50                                                                                                                                                                   ` Robert Haas <robertmhaas@gmail.com>
2019-04-01 00:24                                                                                                                                                                     ` David Rowley <david.rowley@2ndquadrant.com>
2019-03-30 16:06                                                                                                                                                             ` Amit Langote <amitlangote09@gmail.com>
2019-03-31 02:45                                                                                                                                                               ` Imai Yoshikazu <yoshikazu_i443@live.jp>
2019-03-31 05:40                                                                                                                                                                 ` Amit Langote <amitlangote09@gmail.com>
2019-03-31 17:04                                                                                                                                                                   ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-31 18:46                                                                                                                                                                     ` Tom Lane <tgl@sss.pgh.pa.us>
2019-04-01 08:37                                                                                                                                                                       ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-04-01 14:29                                                                                                                                                                       ` Tom Lane <tgl@sss.pgh.pa.us>
2019-04-01 04:55                                                                                                                                                                     ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-03-21 12:45                                                                                                                 ` Jesper Pedersen <jesper.pedersen@redhat.com>
2019-02-18 00:57                                                               ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-02-08 06:40                                                           ` Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
2019-02-08 06:51                                                             ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-02-08 07:43                                                               ` Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
2019-01-09 14:41                                       ` Alvaro Herrera <alvherre@2ndquadrant.com>
2019-01-09 15:16                                         ` Amit Langote <amitlangote09@gmail.com>
2019-01-10 08:41                                           ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-01-10 09:12                                             ` David Rowley <david.rowley@2ndquadrant.com>
2019-01-10 17:56                                             ` Alvaro Herrera <alvherre@2ndquadrant.com>
2019-01-10 21:47                                               ` David Rowley <david.rowley@2ndquadrant.com>
2019-01-11 02:07                                                 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-01-11 06:24                                                   ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-01-16 19:32                                                     ` Alvaro Herrera <alvherre@2ndquadrant.com>
2019-01-17 02:17                                                       ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-01-21 09:01                                                         ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-01-21 14:40                                                           ` Jesper Pedersen <jesper.pedersen@redhat.com>
2019-01-23 01:35                                                           ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-01-24 06:09                                                             ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-01-28 01:44                                                               ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-01-29 10:11                                                                 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-01-30 03:11                                                                   ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-01-30 03:25                                                                     ` Alvaro Herrera <alvherre@2ndquadrant.com>
2019-01-30 04:23                                                                       ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-01-28 05:59                                                               ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-01-11 01:18                                               ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-01-11 04:01                                             ` David Rowley <david.rowley@2ndquadrant.com>
2019-01-11 06:10                                               ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-01-11 06:52                                                 ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-01-10 06:37                                       ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-01-09 02:08                             ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-01-17 10:24                               ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-01-18 05:12                                 ` Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
2019-01-18 05:28                                   ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-01-21 00:51                                     ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-01-21 00:45                                 ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-01-21 01:31                                   ` David Rowley <david.rowley@2ndquadrant.com>
2019-01-22 02:32                                     ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-01-22 05:58                                       ` Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
2019-01-22 07:00                                         ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-01-22 09:47                                           ` David Rowley <david.rowley@2ndquadrant.com>
2019-01-22 13:31                                             ` 今井 良一 <yoshikazu_i443@live.jp>
2019-01-23 01:21                                             ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-02-06 02:03                                               ` Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
2019-02-08 01:34                                                 ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-02-08 01:45                                                   ` David Rowley <david.rowley@2ndquadrant.com>
2019-02-08 01:50                                                     ` Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
2019-02-08 02:39                                                       ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2019-02-08 03:41                                                         ` Tsunakawa, Takayuki <tsunakawa.takay@jp.fujitsu.com>
2019-02-08 08:47                                                   ` Imai, Yoshikazu <imai.yoshikazu@jp.fujitsu.com>
2019-01-21 04:36                                   ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-10-16 01:58     ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-08-31 10:48 ` Kato, Sho <kato-sho@jp.fujitsu.com>
2018-09-03 05:12   ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-09-04 05:45     ` Kato, Sho <kato-sho@jp.fujitsu.com>
2018-09-03 11:57 ` Dilip Kumar <dilipbalaut@gmail.com>
2018-09-04 07:14   ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-09-13 15:33     ` Dilip Kumar <dilipbalaut@gmail.com>
2018-09-04 13:24 ` David Rowley <david.rowley@2ndquadrant.com>
2018-09-05 05:25   ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2018-09-11 01:11 ` Peter Geoghegan <pg@bowt.ie>
2018-09-11 01:42   ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>

This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox